Shipment Tracking Timeline
blocklogisticsFlexport & Maersk style container freight tracking timeline with live AIS vessel telemetry, voyage ETA milestones, Reefer cold-chain specifications, and multimodal transport logistics.
Installation
$pnpm dlx uipkge-ng@latest add shipment-tracking-timeline$npx uipkge-ng@latest add shipment-tracking-timeline$yarn dlx uipkge-ng@latest add shipment-tracking-timeline$bunx uipkge-ng@latest add shipment-tracking-timelineManifest:
https://uipkge.dev/r/angular/shipment-tracking-timeline.jsonInstalls to:src/app/components/blocks/shipment-tracking-timeline/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.
Milestoneinterface Milestone {
id: string
stepNumber: number
title: string
location: string
facility: string
country: string
date: string
status: MilestoneStatus
description: string
coordinates?: string
telemetry?: {
speed?: string
heading?: string
depth?: string
}
}Files installed (2)
src/app/components/blocks/shipment-tracking-timeline/shipment-tracking-timeline.component.ts37.3 kB
// Flexport / Maersk style container freight tracking timeline: header card with vessel // and route meta, 4 voyage metric cards, a filterable multimodal journey milestone stepper // with live AIS telemetry, plus container specs and shipping documentation. Port of the // React block 1:1. import { ChangeDetectionStrategy, Component, Input, computed, signal } from '@angular/core' import { Anchor, ArrowRight, Box, CalendarClock, Check, Compass, Copy, Download, FileCheck2, FileText, LucideAngularModule, MapPin, Navigation, Radio, Search, Share2, ShieldCheck, Ship, Thermometer, Timer, } from 'lucide-angular' import { cn } from '@/lib/utils' import { UiBadgeComponent } from '@/ui/badge/badge.component' import { UiButtonComponent } from '@/ui/button/button.component' import { UiCardComponent, UiCardContentComponent, UiCardDescriptionComponent, UiCardHeaderComponent, UiCardTitleComponent, } from '@/ui/card/card.component' import { UiInputComponent } from '@/ui/input/input.component' import { UiSeparatorComponent } from '@/ui/separator/separator.component' export type MilestoneStatus = 'completed' | 'current' | 'upcoming' export type MilestoneFilter = 'all' | 'completed' | 'upcoming' export interface Milestone { id: string stepNumber: number title: string location: string facility: string country: string date: string status: MilestoneStatus description: string coordinates?: string telemetry?: { speed?: string heading?: string depth?: string } } const defaultMilestones: Milestone[] = [ { id: 'ms-1', stepNumber: 1, title: 'Cargo Loaded at Factory', location: 'Ningbo Depot', facility: 'Apex Logistics Hub South', country: 'CN', date: 'Aug 06, 2026 · 08:30 CST', status: 'completed', description: 'Container stuffed, Reefer unit pre-cooled to -18°C, bolt seal #SL-94021 applied.', }, { id: 'ms-2', stepNumber: 2, title: 'Export Customs Cleared', location: 'Shanghai Port', facility: 'Yangshan Customs Gate 4', country: 'CN', date: 'Aug 08, 2026 · 14:15 CST', status: 'completed', description: 'Export declaration approved. Customs electronic release note #CN-SH-992 issued.', }, { id: 'ms-3', stepNumber: 3, title: 'Vessel Departed Port', location: 'Shanghai Port (CNSHA)', facility: 'Yangshan Deepwater Terminal Berth 3', country: 'CN', date: 'Aug 10, 2026 · 22:00 CST', status: 'completed', description: 'Loaded onto vessel Ever Given (Voyage V.042W, Bay 42, Tier 04). Ocean transit started.', }, { id: 'ms-4', stepNumber: 4, title: 'Current Position · Sea Transit', location: 'Red Sea International Waters', facility: 'Maritime Convoy Corridor 2', country: 'INT', date: 'Aug 21, 2026 · 11:20 UTC (Live)', status: 'current', description: 'Cruising in transit towards Suez Canal. High-frequency AIS beacon reporting healthy telemetry.', coordinates: "24°18'N, 37°42'E", telemetry: { speed: '18.4 knots', heading: '315° NW', depth: '840 m', }, }, { id: 'ms-5', stepNumber: 5, title: 'Suez Canal Transit', location: 'Port Said / Suez (EGPSD)', facility: 'Suez Canal Authority Convoy Hub', country: 'EG', date: 'Est. Aug 25, 2026 · 06:00 EEST', status: 'upcoming', description: 'Scheduled northbound convoy passage slot #04 with authorized maritime pilotage.', }, { id: 'ms-6', stepNumber: 6, title: 'Port of Discharge Arrival', location: 'Rotterdam (NLRTM)', facility: 'APM Terminals Maasvlakte II', country: 'NL', date: 'Est. Sep 04, 2026 · 14:00 CEST', status: 'upcoming', description: 'Vessel berthing & container unlashing operations. Import customs clearance processing.', }, { id: 'ms-7', stepNumber: 7, title: 'Final Destination Delivery', location: 'Duisburg Distribution Center', facility: 'EuroHub Logistics Terminal', country: 'DE', date: 'Est. Sep 07, 2026 · 09:00 CEST', status: 'upcoming', description: 'Intermodal electric rail transfer from Rotterdam to Duisburg bonded warehouse.', }, ] @Component({ changeDetection: ChangeDetectionStrategy.Eager, selector: 'ui-shipment-tracking-timeline, [ui-shipment-tracking-timeline]', standalone: true, // React renders the root <div> itself: the host stays out of layout and `class` goes to the root. host: { '[attr.class]': '"contents"' }, imports: [ LucideAngularModule, UiBadgeComponent, UiButtonComponent, UiCardComponent, UiCardContentComponent, UiCardDescriptionComponent, UiCardHeaderComponent, UiCardTitleComponent, UiInputComponent, UiSeparatorComponent, ], template: ` <div data-slot="shipment-tracking-timeline" [class]="rootClass"> <!-- Header Card --> <div ui-card class="border-border bg-card shadow-xs"> <div ui-card-content class="p-4 sm:p-6"> <div class="flex flex-col gap-5 lg:flex-row lg:items-center lg:justify-between"> <!-- Left: ID, Vessel & Route --> <div class="space-y-3"> <div class="flex flex-wrap items-center gap-2 sm:gap-3"> <div class="flex items-center gap-1.5"> <span class="text-foreground font-mono text-xl font-bold tracking-tight sm:text-2xl"> #MSKU-9482014 </span> <button ui-button variant="ghost" size="icon-sm" class="text-muted-foreground hover:text-foreground size-7" title="Copy Container ID" (click)="copyContainerId()" > @if (copiedContainerId()) { <lucide-icon [img]="Check" class="text-success size-3.5" aria-hidden="true" /> } @else { <lucide-icon [img]="Copy" class="size-3.5" aria-hidden="true" /> } <span class="sr-only">Copy container number</span> </button> </div> <span ui-badge variant="secondary" class="border-success/30 bg-success/10 text-success gap-1.5 px-2.5 py-0.5 text-xs font-medium" > <span class="relative flex size-2 shrink-0"> <span class="bg-success absolute inline-flex h-full w-full rounded-full opacity-75"></span> <span class="bg-success relative inline-flex size-2 rounded-full"></span> </span> In Transit · On Schedule </span> <span ui-badge variant="outline" class="border-border bg-muted/30 text-muted-foreground gap-1 text-xs"> <lucide-icon [img]="Ship" class="text-primary size-3" aria-hidden="true" /> Ocean Freight · FCL 40ft High Cube </span> </div> <!-- Route and Vessel meta --> <div class="flex flex-wrap items-center gap-x-4 gap-y-2 text-xs"> <div class="text-foreground flex items-center gap-1.5 font-medium"> <span class="bg-muted rounded px-1.5 py-0.5 font-mono text-xs">CNSHA</span> <span>Shanghai</span> <lucide-icon [img]="ArrowRight" class="text-muted-foreground size-3.5" aria-hidden="true" /> <span class="bg-muted rounded px-1.5 py-0.5 font-mono text-xs">NLRTM</span> <span>Rotterdam</span> </div> <ui-separator orientation="vertical" class="hidden h-3.5 sm:block" /> <div class="text-muted-foreground flex items-center gap-1.5"> <lucide-icon [img]="Ship" class="text-muted-foreground size-3.5" aria-hidden="true" /> <span>Vessel:</span> <span class="text-foreground font-semibold">Ever Given / V.042W</span> <span class="text-muted-foreground font-mono text-xs">(IMO 9811000)</span> </div> <ui-separator orientation="vertical" class="hidden h-3.5 sm:block" /> <div class="text-muted-foreground flex items-center gap-1"> <span>Carrier:</span> <span class="text-foreground font-medium">Maersk Line</span> </div> </div> </div> <!-- Right: Container search & Quick Actions --> <div class="flex flex-col gap-2 sm:flex-row sm:items-center"> <div class="relative w-full sm:w-64"> <lucide-icon [img]="Search" class="text-muted-foreground absolute top-1/2 left-2.5 size-3.5 -translate-y-1/2" aria-hidden="true" /> <ui-input [value]="searchQuery()" (valueChange)="searchQuery.set($event)" placeholder="Track Container / B/L..." class="h-9 pl-8 font-mono text-xs focus-visible:ring-2" /> </div> <div class="flex items-center gap-2"> <button ui-button size="sm" class="gap-1.5 shadow-xs"> <lucide-icon [img]="Search" class="size-3.5" aria-hidden="true" /> Track </button> <button ui-button variant="outline" size="sm" class="gap-1.5 shadow-xs" (click)="copyTrackingLink()"> <lucide-icon [img]="Share2" class="size-3.5" aria-hidden="true" /> <span class="hidden sm:inline">{{ copiedBLLink() ? 'Copied' : 'Share' }}</span> </button> </div> </div> </div> </div> </div> <!-- 4 Summary Metric Cards --> <div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4"> <!-- ETA Card --> <div ui-card class="border-border bg-card shadow-xs"> <div ui-card-content class="p-4 sm:p-5"> <div class="flex items-start justify-between"> <div class="space-y-1"> <p class="text-muted-foreground text-xs font-medium tracking-wide uppercase">Estimated Arrival (ETA)</p> <p class="text-foreground font-mono text-base font-bold tabular-nums sm:text-lg">Sep 04, 2026</p> <p class="text-muted-foreground text-xs tabular-nums">14:00 CEST · Berth 4A confirmed</p> </div> <div class="bg-primary/10 text-primary flex size-9 shrink-0 items-center justify-center rounded-lg"> <lucide-icon [img]="CalendarClock" class="size-4.5" aria-hidden="true" /> </div> </div> <div class="border-border/60 bg-muted/40 mt-3.5 flex items-center justify-between rounded-md border px-2.5 py-1.5 text-xs" > <span class="text-muted-foreground">Remaining</span> <span class="text-success font-medium">14 days left</span> </div> </div> </div> <!-- Days in Transit Card --> <div ui-card class="border-border bg-card shadow-xs"> <div ui-card-content class="p-4 sm:p-5"> <div class="flex items-start justify-between"> <div class="space-y-1"> <p class="text-muted-foreground text-xs font-medium tracking-wide uppercase">Days in Transit</p> <p class="text-foreground font-mono text-base font-bold tabular-nums sm:text-lg"> 18 <span class="text-muted-foreground text-sm font-normal">of 28 days</span> </p> <p class="text-muted-foreground text-xs tabular-nums">64% of voyage completed</p> </div> <div class="bg-success/10 text-success flex size-9 shrink-0 items-center justify-center rounded-lg"> <lucide-icon [img]="Timer" class="size-4.5" aria-hidden="true" /> </div> </div> <div class="mt-3.5 space-y-1.5"> <div class="bg-muted h-1.5 w-full overflow-hidden rounded-full"> <div class="bg-success h-full rounded-full transition-[width]" style="width: 64%"></div> </div> <div class="text-muted-foreground flex items-center justify-between text-xs"> <span>Departed: Aug 10</span> <span>ETA: Sep 04</span> </div> </div> </div> </div> <!-- Current Coordinates Card --> <div ui-card class="border-border bg-card shadow-xs"> <div ui-card-content class="p-4 sm:p-5"> <div class="flex items-start justify-between"> <div class="space-y-1"> <p class="text-muted-foreground text-xs font-medium tracking-wide uppercase">Current Vessel Position</p> <p class="text-foreground font-mono text-base font-bold tabular-nums sm:text-lg">24°18'N, 37°42'E</p> <p class="text-muted-foreground text-xs"> Red Sea · Speed <span class="text-foreground font-mono font-medium">18.4 kn</span> </p> </div> <div class="bg-info/10 text-info flex size-9 shrink-0 items-center justify-center rounded-lg"> <lucide-icon [img]="Compass" class="size-4.5" aria-hidden="true" /> </div> </div> <div class="border-border/60 bg-muted/40 mt-3.5 flex items-center justify-between rounded-md border px-2.5 py-1.5 text-xs" > <span class="text-muted-foreground">Heading</span> <span class="text-foreground font-mono font-medium">315° NW (Convoy)</span> </div> </div> </div> <!-- Next Milestone Port Card --> <div ui-card class="border-border bg-card shadow-xs"> <div ui-card-content class="p-4 sm:p-5"> <div class="flex items-start justify-between"> <div class="space-y-1"> <p class="text-muted-foreground text-xs font-medium tracking-wide uppercase">Next Milestone Port</p> <p class="text-foreground font-mono text-base font-bold sm:text-lg">Suez Canal</p> <p class="text-muted-foreground text-xs tabular-nums">Aug 25 · Northbound Convoy</p> </div> <div class="bg-warning/10 text-warning flex size-9 shrink-0 items-center justify-center rounded-lg"> <lucide-icon [img]="Anchor" class="size-4.5" aria-hidden="true" /> </div> </div> <div class="border-border/60 bg-muted/40 mt-3.5 flex items-center justify-between rounded-md border px-2.5 py-1.5 text-xs" > <span class="text-muted-foreground">Pilotage Slot</span> <span class="text-foreground font-mono font-medium">Slot #04 (06:00 EEST)</span> </div> </div> </div> </div> <!-- Multimodal Journey Milestones Stepper Card --> <div ui-card class="border-border bg-card shadow-xs"> <div ui-card-header class="flex flex-col gap-3 pb-4 sm:flex-row sm:items-center sm:justify-between"> <div class="space-y-1"> <div class="flex items-center gap-2"> <lucide-icon [img]="Navigation" class="text-primary size-4.5" aria-hidden="true" /> <h3 ui-card-title class="text-base font-semibold">Multimodal Journey Milestones</h3> <span ui-badge variant="outline" class="font-mono text-xs">7 Milestones</span> </div> <p ui-card-description class="text-xs"> End-to-end container tracking from factory loading in Ningbo to warehouse in Duisburg. </p> </div> <!-- Filter Buttons --> <div class="border-border bg-muted/40 flex items-center gap-1 rounded-lg border p-0.5 text-xs"> <button type="button" [class]="filterButtonClass('all')" (click)="activeFilter.set('all')" > All (7) </button> <button type="button" [class]="filterButtonClass('completed')" (click)="activeFilter.set('completed')" > Completed (3) </button> <button type="button" [class]="filterButtonClass('upcoming')" (click)="activeFilter.set('upcoming')" > Upcoming (3) </button> </div> </div> <div ui-card-content class="space-y-6 pt-2"> <!-- Live AIS Telemetry Beacon Banner --> <div class="border-primary/30 bg-primary/5 relative overflow-hidden rounded-xl border p-4"> <div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between"> <div class="flex items-center gap-3"> <div class="bg-primary/10 text-primary relative flex size-10 items-center justify-center rounded-lg"> <lucide-icon [img]="Radio" class="size-5" aria-hidden="true" /> <span class="absolute -top-1 -right-1 flex size-3"> <span class="bg-success absolute inline-flex h-full w-full rounded-full opacity-75"></span> <span class="bg-success relative inline-flex size-3 rounded-full"></span> </span> </div> <div> <div class="flex items-center gap-2"> <h4 class="text-foreground text-sm font-semibold">Active AIS Sea Telemetry</h4> <span ui-badge variant="secondary" class="bg-success/10 text-success text-xs"> Live Transponder </span> </div> <p class="text-muted-foreground text-xs"> Vessel Ever Given navigating Red Sea corridor · Last ping: <span class="text-foreground font-mono font-medium">12s ago</span> </p> </div> </div> <div class="flex flex-wrap items-center gap-3 font-mono text-xs"> <div class="border-border/80 bg-background/80 rounded-md border px-2.5 py-1.5"> <span class="text-muted-foreground">Speed: </span> <span class="text-foreground font-semibold tabular-nums">18.4 knots</span> </div> <div class="border-border/80 bg-background/80 rounded-md border px-2.5 py-1.5"> <span class="text-muted-foreground">Heading: </span> <span class="text-foreground font-semibold">315° NW</span> </div> <div class="border-border/80 bg-background/80 rounded-md border px-2.5 py-1.5"> <span class="text-muted-foreground">Coords: </span> <span class="text-foreground font-semibold tabular-nums">24°18'N, 37°42'E</span> </div> </div> </div> </div> <!-- Connected Timeline Stepper --> <div class="relative pl-6 sm:pl-8"> <!-- Vertical Track Line --> <div class="bg-border absolute top-3 bottom-3 left-3 w-0.5 -translate-x-1/2 sm:left-4"></div> <div class="space-y-6"> @for (milestone of filteredMilestones(); track milestone.id) { <div class="group relative flex flex-col gap-2"> <!-- Step Node Circle --> <div class="absolute -left-6 flex items-center justify-center sm:-left-8"> @if (milestone.status === 'completed') { <div class="ring-background bg-success bg-success flex size-6 items-center justify-center rounded-full text-white shadow-2xs ring-4 sm:size-8 dark:text-zinc-950" > <lucide-icon [img]="Check" class="size-3.5 stroke-[2.5] sm:size-4" aria-hidden="true" /> </div> } @else if (milestone.status === 'current') { <div class="border-primary bg-background text-primary ring-background relative flex size-6 items-center justify-center rounded-full border-2 shadow-xs ring-4 sm:size-8" > <span class="relative flex size-2.5 sm:size-3"> <span class="bg-primary absolute inline-flex h-full w-full rounded-full opacity-75"></span> <span class="bg-primary relative inline-flex size-2.5 rounded-full sm:size-3"></span> </span> </div> } @else { <div class="border-border bg-muted/60 text-muted-foreground ring-background flex size-6 items-center justify-center rounded-full border ring-4 sm:size-8" > <span class="font-mono text-xs font-medium">{{ milestone.stepNumber }}</span> </div> } </div> <!-- Milestone Content Card --> <div [class]="milestoneCardClass(milestone.status)"> <div class="flex flex-col gap-2 sm:flex-row sm:items-start sm:justify-between"> <div class="space-y-1"> <div class="flex flex-wrap items-center gap-2"> <span class="text-foreground text-sm font-semibold">{{ milestone.title }}</span> @if (milestone.status === 'completed') { <span ui-badge variant="secondary" class="bg-success/10 text-success text-xs font-medium"> Completed </span> } @else if (milestone.status === 'current') { <span ui-badge class="bg-primary text-primary-foreground text-xs font-medium"> Active Now </span> } @else { <span ui-badge variant="outline" class="border-border text-muted-foreground text-xs"> Upcoming </span> } <span class="text-muted-foreground font-mono text-xs"> ({{ milestone.facility }}) </span> </div> <p class="text-muted-foreground text-xs">{{ milestone.description }}</p> </div> <div class="shrink-0 text-left sm:text-right"> <span class="text-foreground font-mono text-xs font-medium tabular-nums"> {{ milestone.date }} </span> <div class="text-muted-foreground mt-0.5 flex items-center gap-1 text-xs sm:justify-end"> <lucide-icon [img]="MapPin" class="size-3" aria-hidden="true" /> <span>{{ milestone.location }}</span> </div> </div> </div> @if (milestone.telemetry) { <div class="border-primary/20 bg-background/90 mt-3 grid grid-cols-2 gap-2 rounded-lg border p-2.5 font-mono text-xs sm:grid-cols-4" > <div> <span class="text-muted-foreground">Speed: </span> <span class="text-foreground font-semibold tabular-nums">{{ milestone.telemetry!.speed }}</span> </div> <div> <span class="text-muted-foreground">Heading: </span> <span class="text-foreground font-semibold">{{ milestone.telemetry!.heading }}</span> </div> <div> <span class="text-muted-foreground">Water Depth: </span> <span class="text-foreground font-semibold tabular-nums">{{ milestone.telemetry!.depth }}</span> </div> <div> <span class="text-muted-foreground">AIS Status: </span> <span class="text-success font-semibold">Broadcasting</span> </div> </div> } </div> </div> } </div> </div> </div> </div> <!-- 2-Column Grid: Specifications & Documentation Manifest --> <div class="grid grid-cols-1 gap-6 lg:grid-cols-12"> <!-- Container Specifications & Cold Chain Card --> <div class="space-y-6 lg:col-span-7"> <div ui-card class="border-border bg-card shadow-xs"> <div ui-card-header class="pb-4"> <div class="flex items-center justify-between"> <div class="flex items-center gap-2"> <lucide-icon [img]="Box" class="text-primary size-4.5" aria-hidden="true" /> <h3 ui-card-title class="text-base font-semibold">Container & Cold Chain Specifications</h3> </div> <span ui-badge variant="outline" class="font-mono text-xs">ISO 6346 Verified</span> </div> <p ui-card-description class="text-xs"> Physical parameters, high-security seal integrity, and live Reefer temperature control log. </p> </div> <div ui-card-content class="space-y-5"> <!-- Key Metric Chips --> <div class="grid grid-cols-2 gap-3 sm:grid-cols-4"> <div class="border-border bg-muted/30 rounded-lg border p-3"> <span class="text-muted-foreground text-xs">Gross Weight</span> <p class="text-foreground mt-0.5 font-mono text-sm font-bold tabular-nums">24,500 kg</p> <span class="text-muted-foreground text-xs">Max: 30,480 kg</span> </div> <div class="border-border bg-muted/30 rounded-lg border p-3"> <span class="text-muted-foreground text-xs">Bolt Seal #</span> <p class="text-foreground mt-0.5 font-mono text-sm font-bold">SL-94021</p> <span class="text-success text-xs">ISO 17712 High Sec</span> </div> <div class="border-border bg-muted/30 rounded-lg border p-3"> <span class="text-muted-foreground text-xs">Temperature</span> <p class="text-foreground mt-0.5 font-mono text-sm font-bold tabular-nums">-18.0°C</p> <span class="text-success text-xs">Actual: -18.2°C</span> </div> <div class="border-border bg-muted/30 rounded-lg border p-3"> <span class="text-muted-foreground text-xs">Humidity & Vent</span> <p class="text-foreground mt-0.5 font-mono text-sm font-bold tabular-nums">85% RH</p> <span class="text-muted-foreground text-xs">Vent: Closed (0 cbm)</span> </div> </div> <ui-separator /> <!-- Detailed Specifications Table / List --> <div class="grid grid-cols-1 gap-4 text-xs sm:grid-cols-2"> <div class="space-y-2.5"> <div class="flex items-center justify-between"> <span class="text-muted-foreground">Equipment Size:</span> <span class="text-foreground font-medium">40ft High Cube Reefer (40HR)</span> </div> <div class="flex items-center justify-between"> <span class="text-muted-foreground">Tare Weight:</span> <span class="text-foreground font-mono font-medium tabular-nums">3,300 kg</span> </div> <div class="flex items-center justify-between"> <span class="text-muted-foreground">Net Cargo Weight:</span> <span class="text-foreground font-mono font-medium tabular-nums">21,200 kg</span> </div> <div class="flex items-center justify-between"> <span class="text-muted-foreground">Cargo Volume:</span> <span class="text-foreground font-mono font-medium tabular-nums">67.3 CBM</span> </div> </div> <div class="space-y-2.5"> <div class="flex items-center justify-between"> <span class="text-muted-foreground">Reefer Unit:</span> <span class="text-foreground font-medium">Carrier Transicold PrimeLINE</span> </div> <div class="flex items-center justify-between"> <span class="text-muted-foreground">Power Source:</span> <span class="text-foreground font-medium">Vessel 440V 3-Phase Plug</span> </div> <div class="flex items-center justify-between"> <span class="text-muted-foreground">Defrost Cycle:</span> <span class="text-foreground font-mono font-medium">Auto-Defrost (Every 12h)</span> </div> <div class="flex items-center justify-between"> <span class="text-muted-foreground">Cold-Chain Compliance:</span> <span class="text-success font-semibold">HACCP / GDP Certified</span> </div> </div> </div> <!-- Cold Chain Live Sensor Gauge Banner --> <div class="border-border/80 bg-muted/40 flex flex-col gap-3 rounded-lg border p-3 text-xs sm:flex-row sm:items-center sm:justify-between" > <div class="flex items-center gap-2"> <lucide-icon [img]="Thermometer" class="text-primary size-4" aria-hidden="true" /> <span class="text-foreground font-medium">Continuous Temperature Telemetry:</span> <span class="text-muted-foreground font-mono">24-hour variance ±0.3°C (Nominal)</span> </div> <div class="text-success flex items-center gap-1.5 font-medium"> <lucide-icon [img]="ShieldCheck" class="size-3.5" aria-hidden="true" /> <span>Zero Excursions</span> </div> </div> </div> </div> </div> <!-- Commercial Documents & Shipping Manifest Card --> <div class="space-y-6 lg:col-span-5"> <div ui-card class="border-border bg-card shadow-xs"> <div ui-card-header class="pb-4"> <div class="flex items-center justify-between"> <div class="flex items-center gap-2"> <lucide-icon [img]="FileText" class="text-primary size-4.5" aria-hidden="true" /> <h3 ui-card-title class="text-base font-semibold">Shipping Documentation</h3> </div> <span ui-badge variant="secondary" class="text-xs">Customs Released</span> </div> <p ui-card-description class="text-xs"> Bill of Lading, invoice identifiers, and trade compliance references. </p> </div> <div ui-card-content class="space-y-4"> <div class="space-y-2.5 text-xs"> <div class="border-border/60 bg-muted/30 flex items-center justify-between rounded-lg border p-2.5"> <span class="text-muted-foreground">Commercial Invoice #:</span> <span class="text-foreground font-mono font-bold">CI-2026-849</span> </div> <div class="border-border/60 bg-muted/30 flex items-center justify-between rounded-lg border p-2.5"> <span class="text-muted-foreground">Master Bill of Lading (MBL):</span> <span class="text-foreground font-mono font-bold">MAEU-98421094</span> </div> <div class="border-border/60 bg-muted/30 flex items-center justify-between rounded-lg border p-2.5"> <span class="text-muted-foreground">Booking Reference:</span> <span class="text-foreground font-mono font-medium">BKG-7712093</span> </div> <div class="border-border/60 bg-muted/30 flex items-center justify-between rounded-lg border p-2.5"> <span class="text-muted-foreground">Incoterms 2020:</span> <span class="text-foreground font-medium">FOB Shanghai Port</span> </div> </div> <ui-separator /> <!-- Parties: Shipper and Consignee --> <div class="space-y-3 text-xs"> <div> <p class="text-muted-foreground font-medium">Shipper / Exporter</p> <p class="text-foreground font-semibold">Ningbo Apex International Marine Trade Co.</p> <p class="text-muted-foreground">Beilun District, Ningbo, Zhejiang, China</p> </div> <div> <p class="text-muted-foreground font-medium">Consignee / Importer</p> <p class="text-foreground font-semibold">EuroHub Logistics & Cold Storage GmbH</p> <p class="text-muted-foreground">Logport I, 47226 Duisburg, Germany</p> </div> </div> <div class="flex flex-col items-center gap-2 pt-2 sm:flex-row"> <button ui-button aria-label="Download attachment" variant="outline" size="sm" class="min-w-0 justify-center gap-2 text-xs shadow-xs sm:flex-1" > <lucide-icon [img]="Download" class="size-3.5" aria-hidden="true" /> Download Bill of Lading </button> <button ui-button variant="ghost" size="sm" class="text-muted-foreground hover:text-foreground min-w-0 justify-center gap-2 text-xs sm:flex-1" > <lucide-icon [img]="FileCheck2" class="size-3.5" aria-hidden="true" /> Customs Pack (PDF) </button> </div> </div> </div> </div> </div> </div> `, }) export class UiShipmentTrackingTimelineComponent { protected readonly Anchor = Anchor protected readonly ArrowRight = ArrowRight protected readonly Box = Box protected readonly CalendarClock = CalendarClock protected readonly Check = Check protected readonly Compass = Compass protected readonly Copy = Copy protected readonly Download = Download protected readonly FileCheck2 = FileCheck2 protected readonly FileText = FileText protected readonly MapPin = MapPin protected readonly Navigation = Navigation protected readonly Radio = Radio protected readonly Search = Search protected readonly Share2 = Share2 protected readonly ShieldCheck = ShieldCheck protected readonly Ship = Ship protected readonly Thermometer = Thermometer protected readonly Timer = Timer @Input('initialContainerId') set initialContainerIdInput(v: string | undefined) { if (v !== undefined) this.searchQuery.set(v) } @Input('initialFilter') set initialFilterInput(v: MilestoneFilter | undefined) { if (v) this.activeFilter.set(v) } @Input('class') className?: string readonly searchQuery = signal('MSKU-9482014') readonly activeFilter = signal<MilestoneFilter>('all') readonly copiedContainerId = signal(false) readonly copiedBLLink = signal(false) readonly filteredMilestones = computed(() => { const filter = this.activeFilter() if (filter === 'completed') { return defaultMilestones.filter((m) => m.status === 'completed') } if (filter === 'upcoming') { return defaultMilestones.filter((m) => m.status === 'upcoming') } return defaultMilestones }) get rootClass(): string { return cn('w-full space-y-6', this.className) } filterButtonClass(filter: MilestoneFilter): string { // [class] overwrites the whole class attribute: the base layout classes must be merged here. return cn( 'rounded-md px-2.5 py-1 font-medium transition-colors', this.activeFilter() === filter ? 'bg-background text-foreground font-semibold shadow-xs' : 'text-muted-foreground hover:text-foreground', ) } milestoneCardClass(status: MilestoneStatus): string { return cn( 'rounded-xl border p-4 transition-colors', status === 'current' ? 'border-primary/50 bg-primary/5 ring-primary/20 shadow-xs ring-1' : status === 'completed' ? 'border-border/80 bg-card/60 hover:bg-muted/30' : 'border-border/60 bg-muted/20 opacity-80 hover:opacity-100', ) } copyContainerId(): void { if (typeof navigator !== 'undefined' && navigator.clipboard) { navigator.clipboard.writeText('MSKU-9482014') this.copiedContainerId.set(true) setTimeout(() => { this.copiedContainerId.set(false) }, 2000) } } copyTrackingLink(): void { if (typeof navigator !== 'undefined' && navigator.clipboard) { navigator.clipboard.writeText('https://uipkge.dev/track/MSKU-9482014') this.copiedBLLink.set(true) setTimeout(() => { this.copiedBLLink.set(false) }, 2000) } } }src/app/components/blocks/shipment-tracking-timeline/index.ts0.2 kB
export { UiShipmentTrackingTimelineComponent, type MilestoneStatus, type MilestoneFilter, type Milestone, } from './shipment-tracking-timeline.component'
Raw manifest:https://uipkge.dev/r/angular/shipment-tracking-timeline.json