Bill Of Lading Viewer
blocklogisticsMaritime & Air Bill of Lading (BOL) shipping document viewer and customs compliance inspector with multi-party entity grid, transport routing, container & cargo manifest, freight terms, carrier stamp, and blockchain verification.
Installation
$pnpm dlx uipkge-ng@latest add bill-of-lading-viewer$npx uipkge-ng@latest add bill-of-lading-viewer$yarn dlx uipkge-ng@latest add bill-of-lading-viewer$bunx uipkge-ng@latest add bill-of-lading-viewerManifest:
https://uipkge.dev/r/angular/bill-of-lading-viewer.jsonInstalls to:src/app/components/blocks/bill-of-lading-viewer/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.
ContainerCargoIteminterface ContainerCargoItem {
id: string
containerNo: string
sealNo: string
containerType: string
packagesCount: number
packagesKind: string
description: string
hsCode: string
grossWeightKg: number
grossWeightLbs: number
volumeCbm: number
hazardClass?: string
temperature?: string
}ComplianceGateinterface ComplianceGate {
id: string
name: string
status: 'passed' | 'warning' | 'pending'
authority: string
reference: string
details: string
}Files installed (2)
src/app/components/blocks/bill-of-lading-viewer/bill-of-lading-viewer.component.ts44.2 kB
// Maritime & Air Bill of Lading (BOL) shipping document viewer and customs compliance // inspector: multi-party entity grid, transport routing, container & cargo manifest with // totals, freight terms, carrier stamp, blockchain verification drawer, and UN/EDIFACT // EDI payload tab. Port of the React block 1:1. import { ChangeDetectionStrategy, Component, DestroyRef, EventEmitter, Input, Output, computed, inject, signal } from '@angular/core' import { AlertCircle, Anchor, Building2, Check, CheckCircle2, Copy, Download, FileText, LucideAngularModule, Mail, MapPin, Phone, Printer, ShieldCheck, Ship, } 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 { UiSeparatorComponent } from '@/ui/separator/separator.component' import { UiTableBodyComponent, UiTableCellComponent, UiTableComponent, UiTableFooterComponent, UiTableHeadComponent, UiTableHeaderComponent, UiTableRowComponent, } from '@/ui/table/table.component' export interface ContainerCargoItem { id: string containerNo: string sealNo: string containerType: string packagesCount: number packagesKind: string description: string hsCode: string grossWeightKg: number grossWeightLbs: number volumeCbm: number hazardClass?: string temperature?: string } export interface ComplianceGate { id: string name: string status: 'passed' | 'warning' | 'pending' authority: string reference: string details: string } export type BillOfLadingTab = 'document' | 'compliance' | 'edi' const cargoItems: ContainerCargoItem[] = [ { id: 'cargo-1', containerNo: 'MSKU-948201-4', sealNo: 'SL-94021', containerType: "40' High Cube (40' HC)", packagesCount: 24, packagesKind: 'Pallets / 480 Cartons', description: 'Consumer Electronics: Pro Audio Studio Headphones, Dynamic Transducers & Wireless DAC Amplifiers', hsCode: '8518.30.00', grossWeightKg: 4850.0, grossWeightLbs: 10692.42, volumeCbm: 32.4, hazardClass: 'UN3481 Class 9 (Lithium Ion Batteries contained in equipment)', temperature: 'Ambient (Dry Van)', }, { id: 'cargo-2', containerNo: 'MSKU-819034-2', sealNo: 'SL-94022', containerType: "40' High Cube (40' HC)", packagesCount: 16, packagesKind: 'Pallets / 320 Cartons', description: 'Audio Accessories: Braided MMCX Oxygen-Free Cables, Velour Replacement Earpads & Hard Travel Cases', hsCode: '8518.90.80', grossWeightKg: 2320.0, grossWeightLbs: 5114.72, volumeCbm: 18.6, hazardClass: 'Non-Hazardous / Standard Cargo', temperature: 'Ambient (Dry Van)', }, ] const complianceGates: ComplianceGate[] = [ { id: 'gate-1', name: 'EU EORI & VAT Identity Verification', status: 'passed', authority: 'European Customs Agency (VIES / EOS)', reference: 'NL864219034', details: 'Consignee EORI validated against EU Economic Operators Registry with valid Dutch VAT matching.', }, { id: 'gate-2', name: 'ICS2 Entry Summary Declaration (ENS)', status: 'passed', authority: 'EU Customs Import Control System 2', reference: 'MRN: 26NL948201083921A', details: 'Pre-arrival cargo security manifest filed 24 hours prior to loading in Shanghai (Approved).', }, { id: 'gate-3', name: 'HS Tariff Classification & Duty Schedule', status: 'passed', authority: 'TARIC European Customs Database', reference: 'HS 8518.30.00 / 8518.90.80', details: 'Eligible for 0.0% EU base tariff rate + standard 21.0% Netherlands Import VAT under Article 23.', }, { id: 'gate-4', name: 'ISO 17712 High-Security Container Seals', status: 'passed', authority: 'International Maritime Organization (IMO)', reference: 'SL-94021 / SL-94022', details: 'High-security mechanical bolt seals verified intact at origin terminal container gate-in.', }, { id: 'gate-5', name: 'IMDG Dangerous Goods UN3481 Compliance', status: 'passed', authority: 'International Maritime Dangerous Goods (IMDG)', reference: 'UN3481 PI966 Sec. II', details: 'Lithium-ion cells packed with equipment certified under special provision 188 for under-deck stowage.', }, { id: 'gate-6', name: 'Global Sanctions & Denied Party Screening', status: 'passed', authority: 'UN, OFAC & EU Consolidated Sanction Lists', reference: 'SCR-2026-99104', details: '0 matches identified across all parties (Shipper, Consignee, Notify Party, Carrier vessel).', }, ] @Component({ changeDetection: ChangeDetectionStrategy.Eager, selector: 'ui-bill-of-lading-viewer, [ui-bill-of-lading-viewer]', 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, UiSeparatorComponent, UiTableBodyComponent, UiTableCellComponent, UiTableComponent, UiTableFooterComponent, UiTableHeadComponent, UiTableHeaderComponent, UiTableRowComponent, ], template: ` <div data-slot="bill-of-lading-viewer" [class]="rootClass"> <!-- Top Action Bar / Document Control Header --> <div class="border-border bg-card flex flex-col gap-4 rounded-xl border p-4 shadow-xs sm:flex-row sm:items-center sm:justify-between" > <div class="space-y-1"> <div class="flex flex-wrap items-center gap-2"> <span ui-badge wrap variant="outline" class="font-mono text-xs tracking-wider uppercase"> {{ scacCode }} • Maritime Ocean BOL </span> <button type="button" class="group focus-visible:ring-ring border-success/30 bg-success/10 text-success hover:bg-success/20 text-success inline-flex min-h-6 items-center gap-1.5 rounded-full border px-2.5 py-0.5 text-xs font-medium transition-colors focus-visible:ring-2 focus-visible:outline-none" (click)="toggleBlockchainDetails()" > <lucide-icon [img]="ShieldCheck" class="size-3.5" /> <span>Verify Blockchain Authenticity</span> <span class="bg-success size-1.5 animate-pulse rounded-full" aria-hidden="true"></span> </button> </div> <div class="flex flex-wrap items-center gap-2 pt-1"> <h1 class="text-xl font-bold tracking-tight sm:text-2xl">{{ documentTitle }}</h1> <span class="text-muted-foreground font-mono text-base font-semibold tabular-nums">{{ bolNumber }}</span> <button ui-button variant="ghost" size="icon" class="text-muted-foreground hover:text-foreground size-7" title="Copy BOL number" (click)="handleCopyBol()" > @if (copiedBol()) { <lucide-icon [img]="Check" class="text-success size-3.5" /> } @else { <lucide-icon [img]="Copy" class="size-3.5" /> } <span class="sr-only">Copy BOL number</span> </button> </div> <p class="text-muted-foreground text-xs"> Carrier: <strong class="text-foreground font-medium">{{ carrierName }}</strong> • Issued: {{ issueDate }} in {{ placeOfIssue }} </p> </div> <!-- Action Buttons & Tab Switcher --> <div class="flex flex-wrap items-center gap-2 sm:self-center"> <div class="border-border bg-muted/40 flex items-center rounded-lg border p-1"> <button type="button" [class]="tabClass('document')" (click)="activeTab.set('document')"> Document View </button> <button type="button" [class]="tabClass('compliance', true)" (click)="activeTab.set('compliance')"> <span>Customs & Compliance</span> <span ui-badge wrap variant="success" class="size-4 justify-center p-0 text-center font-mono text-xs leading-4" >6</span > </button> <button type="button" [class]="tabClass('edi')" (click)="activeTab.set('edi')">Raw EDI / JSON</button> </div> <button ui-button variant="outline" size="sm" class="gap-1.5 text-xs" (click)="handlePrint()"> <lucide-icon [img]="Printer" class="size-3.5" /> <span>Print</span> </button> <button ui-button aria-label="Download attachment" size="sm" class="gap-1.5 text-xs" (click)="handleDownloadPdf()" > <lucide-icon [img]="Download" class="size-3.5" /> <span>Download Printable BOL</span> </button> </div> </div> <!-- Blockchain Cryptographic Proof Drawer / Card --> @if (showVerifyDetails()) { <div class="animate-in fade-in slide-in-from-top-2 border-success/30 bg-success/5 rounded-xl border p-4 text-xs transition-colors" > <div class="flex flex-col gap-3 md:flex-row md:items-center md:justify-between"> <div class="flex items-start gap-3"> <div class="bg-success/10 text-success rounded-lg p-2"> <lucide-icon [img]="ShieldCheck" class="size-5 shrink-0" /> </div> <div class="space-y-1"> <div class="flex items-center gap-2"> <span class="text-foreground font-semibold">Cryptographic Bill of Lading Authenticity Verified</span> <span ui-badge wrap variant="success" class="text-xs">Immutable Ledger Record</span> </div> <p class="text-muted-foreground"> Document state verified on {{ blockchainNetwork }}. Digital hash matches original carrier signature at origin. </p> </div> </div> <div class="flex flex-wrap items-center gap-2 font-mono text-xs"> <span class="bg-background/80 border-border text-foreground rounded border px-2 py-1" >Block #{{ blockchainBlock }}</span > <span class="bg-background/80 border-border text-foreground max-w-xs truncate rounded border px-2 py-1" [attr.title]="blockchainTxHash" > Tx: {{ blockchainTxHash }} </span> </div> </div> </div> } <!-- TAB 1: OFFICIAL BILL OF LADING PAPER DOCUMENT --> @if (activeTab() === 'document') { <div class="border-border bg-card overflow-hidden rounded-xl border shadow-xs print:border-none print:shadow-none" > <!-- Formal Document Header --> <div class="border-border bg-muted/20 border-b p-6 sm:p-8"> <div class="flex flex-col gap-6 lg:flex-row lg:items-start lg:justify-between"> <div class="space-y-3"> <div class="flex items-center gap-3"> <div class="bg-primary text-primary-foreground flex size-10 items-center justify-center rounded-lg shadow-xs" > <lucide-icon [img]="Anchor" class="size-5" /> </div> <div> <h2 class="text-lg font-semibold tracking-tight sm:text-xl">{{ carrierName }}</h2> <p class="text-muted-foreground font-mono text-xs"> SCAC: {{ scacCode }} • Standard Ocean Carrier Bill of Lading </p> </div> </div> <p class="text-muted-foreground max-w-xl text-xs leading-relaxed"> Issued in accordance with the International Convention for the Unification of Certain Rules of Law relating to Bills of Lading (Hague-Visby Rules) and UN Multimodal Transport Guidelines. </p> </div> <div class="border-border bg-background/80 flex flex-col gap-2 rounded-lg border p-4 sm:min-w-[280px]"> <div class="flex items-center justify-between gap-4"> <span class="text-muted-foreground text-xs font-medium">Bill of Lading No.</span> <span class="text-primary font-mono text-sm font-semibold tabular-nums">{{ bolNumber }}</span> </div> <div class="flex items-center justify-between gap-4"> <span class="text-muted-foreground text-xs">Booking Reference:</span> <span class="font-mono text-xs font-semibold tabular-nums">{{ bookingReference }}</span> </div> <div class="flex items-center justify-between gap-4"> <span class="text-muted-foreground text-xs">Export Reference:</span> <span class="font-mono text-xs tabular-nums">{{ exportReference }}</span> </div> <ui-separator class="my-1" /> <div class="flex items-center justify-between gap-4"> <span class="text-muted-foreground text-xs">Document Status:</span> <span ui-badge wrap variant="default" class="text-xs font-medium">Negotiable / Original</span> </div> </div> </div> </div> <!-- Section 1: Parties Grid (Shipper, Consignee, Notify Party) --> <div class="divide-border border-border grid grid-cols-1 divide-y border-b md:grid-cols-3 md:divide-x md:divide-y-0" > <div class="bg-card space-y-3 p-5 sm:p-6"> <div class="flex items-center justify-between"> <span class="text-primary font-mono text-xs font-semibold">1. Shipper / Exporter</span> <lucide-icon [img]="Building2" class="text-muted-foreground size-4" /> </div> <div class="space-y-1.5 text-xs"> <p class="text-foreground text-sm font-semibold">{{ shipperName }}</p> <p class="text-muted-foreground leading-relaxed">{{ shipperAddress }}</p> <div class="text-muted-foreground space-y-1 pt-2"> <p class="flex items-center gap-1.5"> <lucide-icon [img]="Phone" class="text-muted-foreground size-3 shrink-0" /> <span class="tabular-nums">{{ shipperContact }}</span> </p> <p class="flex items-center gap-1.5"> <lucide-icon [img]="Mail" class="text-muted-foreground size-3 shrink-0" /> <span>{{ shipperEmail }}</span> </p> <p class="text-foreground pt-1 font-mono"> <span class="text-muted-foreground">EORI / Tax ID:</span> {{ shipperEori }} </p> </div> </div> </div> <div class="bg-card space-y-3 p-5 sm:p-6"> <div class="flex items-center justify-between"> <span class="text-primary font-mono text-xs font-semibold">2. Consignee / Importer</span> <lucide-icon [img]="MapPin" class="text-muted-foreground size-4" /> </div> <div class="space-y-1.5 text-xs"> <p class="text-foreground text-sm font-semibold">{{ consigneeName }}</p> <p class="text-muted-foreground leading-relaxed">{{ consigneeAddress }}</p> <div class="text-muted-foreground space-y-1 pt-2"> <p class="flex items-center gap-1.5"> <lucide-icon [img]="Phone" class="text-muted-foreground size-3 shrink-0" /> <span class="tabular-nums">{{ consigneeContact }}</span> </p> <p class="flex items-center gap-1.5"> <lucide-icon [img]="Mail" class="text-muted-foreground size-3 shrink-0" /> <span>{{ consigneeEmail }}</span> </p> <p class="text-foreground pt-1 font-mono"> <span class="text-muted-foreground">EORI / VAT:</span> {{ consigneeEori }} </p> </div> </div> </div> <div class="bg-card space-y-3 p-5 sm:p-6"> <div class="flex items-center justify-between"> <span class="text-primary font-mono text-xs font-semibold">3. Notify Party</span> <lucide-icon [img]="FileText" class="text-muted-foreground size-4" /> </div> <div class="space-y-1.5 text-xs"> <p class="text-foreground text-sm font-semibold">{{ notifyPartyName }}</p> <p class="text-muted-foreground leading-relaxed">{{ notifyPartyAddress }}</p> <div class="text-muted-foreground space-y-1 pt-2"> <p class="flex items-center gap-1.5"> <lucide-icon [img]="Phone" class="text-muted-foreground size-3 shrink-0" /> <span class="tabular-nums">{{ notifyPartyContact }}</span> </p> <p class="flex items-center gap-1.5"> <lucide-icon [img]="Mail" class="text-muted-foreground size-3 shrink-0" /> <span>{{ notifyPartyEmail }}</span> </p> <p class="text-foreground pt-1 font-medium"> <span class="text-muted-foreground">Attention:</span> {{ notifyPartyAttention }} </p> </div> </div> </div> </div> <!-- Section 2: Transport & Routing Details Grid --> <div class="border-border bg-muted/10 border-b p-5 sm:p-6"> <div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4"> <div class="border-border bg-card space-y-1 rounded-lg border p-3 shadow-xs"> <span class="text-muted-foreground text-xs font-medium">Pre-Carriage By</span> <p class="text-foreground text-xs font-semibold">{{ preCarriageBy }}</p> <span class="text-muted-foreground text-xs">Receipt: {{ placeOfReceipt }}</span> </div> <div class="border-border bg-card space-y-1 rounded-lg border p-3 shadow-xs"> <span class="text-muted-foreground text-xs font-medium">Ocean Vessel & Voyage</span> <p class="text-foreground flex items-center gap-1.5 text-xs font-semibold"> <lucide-icon [img]="Ship" class="text-primary size-3.5 shrink-0" /> <span>{{ vesselVoyage }}</span> </p> <span class="text-muted-foreground font-mono text-xs">{{ vesselImo }} • {{ vesselFlag }}</span> </div> <div class="border-border bg-card space-y-1 rounded-lg border p-3 shadow-xs"> <span class="text-muted-foreground text-xs font-medium">Port of Loading (POL)</span> <p class="text-foreground text-xs font-semibold">{{ portOfLoading }}</p> <span class="text-muted-foreground text-xs">Departure: Aug 23, 2026</span> </div> <div class="border-border bg-card space-y-1 rounded-lg border p-3 shadow-xs"> <span class="text-muted-foreground text-xs font-medium">Port of Discharge (POD)</span> <p class="text-foreground text-xs font-semibold">{{ portOfDischarge }}</p> <span class="text-muted-foreground text-xs">Delivery: {{ placeOfDelivery }}</span> </div> </div> </div> <!-- Section 3: Container & Cargo Manifest Table --> <div class="space-y-4 p-5 sm:p-6"> <div class="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between"> <div> <h3 class="text-foreground text-sm font-semibold">Container & Cargo Description</h3> <p class="text-muted-foreground text-xs"> Itemized manifest of goods, seal identification, Harmonized Tariff codes, and gross weight </p> </div> <span ui-badge wrap variant="outline" class="w-fit font-mono text-xs" >FCL / FCL • CY-CY Movement</span > </div> <div class="border-border overflow-hidden rounded-lg border"> <div class="overflow-x-auto"> <table ui-table density="cozy"> <thead ui-table-header> <tr ui-table-row> <th ui-table-head class="text-xs">Container No. & Seal No.</th> <th ui-table-head class="text-xs">Packages & Kind</th> <th ui-table-head class="text-xs">Description of Goods</th> <th ui-table-head class="text-xs">HS Tariff Code</th> <th ui-table-head class="text-right text-xs">Gross Wt (kg)</th> <th ui-table-head class="text-right text-xs">Volume (CBM)</th> </tr> </thead> <tbody ui-table-body> @for (item of cargoItemsList; track item.id) { <tr ui-table-row> <td ui-table-cell class="align-top"> <div class="text-foreground font-mono text-xs font-semibold">{{ item.containerNo }}</div> <div class="text-muted-foreground font-mono text-xs">Seal: {{ item.sealNo }}</div> <span ui-badge wrap variant="secondary" class="mt-1 text-xs font-normal">{{ item.containerType }}</span> </td> <td ui-table-cell class="align-top"> <div class="text-foreground text-xs font-medium tabular-nums"> {{ item.packagesCount }} Pallets </div> <div class="text-muted-foreground text-xs">{{ item.packagesKind }}</div> </td> <td ui-table-cell class="max-w-md align-top"> <div class="text-foreground text-xs leading-relaxed font-medium">{{ item.description }}</div> @if (item.hazardClass) { <div class="text-warning mt-1 flex items-center gap-1 text-xs"> <lucide-icon [img]="AlertCircle" class="size-3 shrink-0" /> <span>{{ item.hazardClass }}</span> </div> } </td> <td ui-table-cell class="text-foreground align-top font-mono text-xs tabular-nums"> {{ item.hsCode }} </td> <td ui-table-cell class="text-foreground text-right align-top font-mono text-xs font-semibold tabular-nums" > {{ item.grossWeightKg.toLocaleString('en-US', { minimumFractionDigits: 2 }) }} kg <div class="text-muted-foreground font-normal"> {{ item.grossWeightLbs.toLocaleString('en-US', { minimumFractionDigits: 1 }) }} lbs </div> </td> <td ui-table-cell class="text-foreground text-right align-top font-mono text-xs tabular-nums"> {{ item.volumeCbm.toFixed(2) }} m³ </td> </tr> } </tbody> <tfoot ui-table-footer> <tr ui-table-row> <td ui-table-cell [attr.colspan]="2" class="text-xs font-semibold"> Totals: {{ cargoItemsList.length }} Containers • {{ totalPackagesCount() }} Pallets </td> <td ui-table-cell [attr.colspan]="2" class="text-muted-foreground text-xs"> Dangerous Goods Stowage Declared • Certified Dry Van </td> <td ui-table-cell class="text-foreground text-right font-mono text-xs font-semibold tabular-nums"> {{ totalGrossWeightKg().toLocaleString('en-US', { minimumFractionDigits: 2 }) }} kg </td> <td ui-table-cell class="text-foreground text-right font-mono text-xs font-semibold tabular-nums"> {{ totalVolumeCbm().toFixed(2) }} m³ </td> </tr> </tfoot> </table> </div> </div> </div> <!-- Section 4: Freight Charges & Commercial Terms --> <div class="divide-border border-border grid grid-cols-1 divide-y border-t sm:grid-cols-2 sm:divide-x sm:divide-y-0 lg:grid-cols-4" > <div class="bg-muted/10 space-y-1.5 p-5"> <span class="text-muted-foreground text-xs font-medium">Freight & Charges</span> <div class="flex items-center gap-2"> <span ui-badge wrap variant="default" class="text-xs font-medium tracking-wide uppercase">{{ freightTerms }}</span> </div> <p class="text-muted-foreground pt-1 text-xs"> Payable at: <strong class="text-foreground">{{ freightPayableAt }}</strong> </p> </div> <div class="bg-muted/10 space-y-1.5 p-5"> <span class="text-muted-foreground text-xs font-medium">Declared Value (Customs)</span> <p class="text-foreground font-mono text-sm font-semibold tabular-nums">{{ declaredValue }}</p> <p class="text-muted-foreground text-xs">Commercial Invoice #INV-2026-8942</p> </div> <div class="bg-muted/10 space-y-1.5 p-5"> <span class="text-muted-foreground text-xs font-medium">Marine Cargo Insurance</span> <p class="text-foreground font-mono text-xs font-semibold">{{ insuranceCert }}</p> <p class="text-muted-foreground text-xs">Institute Cargo Clauses (A) All Risks</p> </div> <div class="bg-muted/10 space-y-1.5 p-5"> <span class="text-muted-foreground text-xs font-medium">Incoterms & Letter of Credit</span> <p class="text-foreground text-xs font-semibold">{{ incoterms }}</p> <p class="text-muted-foreground truncate font-mono text-xs" [attr.title]="letterOfCredit"> {{ letterOfCredit }} </p> </div> </div> <!-- Section 5: Carrier Signature & Authorization Stamp --> <div class="border-border bg-card border-t p-6 sm:p-8"> <div class="grid grid-cols-1 gap-6 md:grid-cols-12 md:items-center"> <div class="space-y-2 md:col-span-6"> <span class="text-primary text-xs font-semibold">Carrier Authorization & Endorsement</span> <p class="text-muted-foreground text-xs leading-relaxed"> IN WITNESS whereof the carrier, through its authorized master or port agent, has signed three (3) original Bills of Lading, one of which being accomplished, the others to stand void. </p> <div class="text-muted-foreground flex flex-col gap-1 pt-2 font-mono text-xs"> <p> <span class="text-foreground font-semibold">Authorized Agent:</span> Capt. H. van der Meer (Master / Port Agent) </p> <p> <span class="text-foreground font-semibold">Digital Signature SHA-256:</span> 9b2d8fe14157a3e8...c72194b0 </p> <p> <span class="text-foreground font-semibold">Issued:</span> {{ issueDate }} at Port of Shanghai, China </p> </div> </div> <div class="flex justify-center md:col-span-3"> <div class="border-primary/40 bg-primary/5 text-primary rotate-[-2deg] rounded-lg border-2 border-dashed p-4 text-center font-mono text-xs tracking-widest uppercase shadow-xs" > <div class="mb-1 flex justify-center"> <lucide-icon [img]="Ship" class="text-primary size-6" /> </div> <div class="text-sm font-semibold tracking-tight">MAERSK LINE A/S</div> <div class="text-muted-foreground text-xs font-semibold">PORT OF SHANGHAI AGENCY</div> <div class="border-primary/30 text-success mt-1 border-t pt-1 text-xs font-semibold"> OFFICIALLY ENDORSED </div> </div> </div> <div class="border-border bg-muted/20 flex flex-col items-center justify-center rounded-lg border p-4 text-center md:col-span-3" > <svg viewBox="0 0 100 100" class="text-foreground size-20 shrink-0" fill="currentColor" aria-label="QR Code" > <rect x="2" y="2" width="28" height="28" rx="3" fill="none" stroke="currentColor" stroke-width="4" /> <rect x="10" y="10" width="12" height="12" rx="1.5" /> <rect x="70" y="2" width="28" height="28" rx="3" fill="none" stroke="currentColor" stroke-width="4" /> <rect x="78" y="10" width="12" height="12" rx="1.5" /> <rect x="2" y="70" width="28" height="28" rx="3" fill="none" stroke="currentColor" stroke-width="4" /> <rect x="10" y="78" width="12" height="12" rx="1.5" /> <rect x="36" y="6" width="6" height="6" rx="1" /> <rect x="48" y="6" width="6" height="6" rx="1" /> <rect x="58" y="6" width="6" height="6" rx="1" /> <rect x="6" y="36" width="6" height="6" rx="1" /> <rect x="18" y="36" width="6" height="6" rx="1" /> <rect x="36" y="36" width="6" height="6" rx="1" /> <rect x="48" y="36" width="6" height="6" rx="1" /> <rect x="58" y="36" width="6" height="6" rx="1" /> <rect x="74" y="36" width="6" height="6" rx="1" /> <rect x="86" y="36" width="6" height="6" rx="1" /> <rect x="36" y="48" width="6" height="6" rx="1" /> <rect x="48" y="48" width="6" height="6" rx="1" /> <rect x="58" y="48" width="6" height="6" rx="1" /> <rect x="36" y="60" width="6" height="6" rx="1" /> <rect x="48" y="60" width="6" height="6" rx="1" /> <rect x="58" y="60" width="6" height="6" rx="1" /> <rect x="36" y="76" width="6" height="6" rx="1" /> <rect x="48" y="76" width="6" height="6" rx="1" /> <rect x="58" y="76" width="6" height="6" rx="1" /> <rect x="74" y="76" width="6" height="6" rx="1" /> <rect x="86" y="76" width="6" height="6" rx="1" /> <rect x="74" y="86" width="6" height="6" rx="1" /> <rect x="86" y="86" width="6" height="6" rx="1" /> </svg> <span class="text-foreground mt-2 font-mono text-xs font-medium" >Scan for Port Customs e-Verification</span > <span class="text-muted-foreground text-xs">UIPKGE Trust Protocol</span> </div> </div> </div> </div> } <!-- TAB 2: CUSTOMS & COMPLIANCE INSPECTOR --> @if (activeTab() === 'compliance') { <div class="space-y-6"> <div class="grid grid-cols-1 gap-4 sm:grid-cols-3"> <div ui-card class="border-border bg-card"> <div ui-card-header class="p-4 pb-2"> <p ui-card-description class="text-xs">Customs Risk Assessment</p> <div class="flex items-baseline gap-2"> <h3 ui-card-title class="text-success text-2xl font-bold">99 / 100</h3> <span ui-badge wrap variant="success" class="text-xs">Low Risk</span> </div> </div> <div ui-card-content class="text-muted-foreground p-4 pt-1 text-xs"> Green channel clearance granted for Rotterdam entry. Pre-arrival ICS2 security declaration verified. </div> </div> <div ui-card class="border-border bg-card"> <div ui-card-header class="p-4 pb-2"> <p ui-card-description class="text-xs">Import Duties & VAT Estimation</p> <div class="flex items-baseline gap-2"> <h3 ui-card-title class="text-foreground font-mono text-2xl font-bold">$29,925.00</h3> <span ui-badge wrap variant="outline" class="font-mono text-xs">EUR 27,665</span> </div> </div> <div ui-card-content class="text-muted-foreground p-4 pt-1 text-xs"> 0% Base Duty (TARIC 8518.30.00) + 21.0% Netherlands Import VAT under Article 23 reverse-charge mechanism. </div> </div> <div ui-card class="border-border bg-card"> <div ui-card-header class="p-4 pb-2"> <p ui-card-description class="text-xs">Container Seal Audit</p> <div class="flex items-baseline gap-2"> <h3 ui-card-title class="text-foreground text-2xl font-bold">2 / 2 Valid</h3> <span ui-badge wrap variant="success" class="text-xs">ISO 17712 Compliant</span> </div> </div> <div ui-card-content class="text-muted-foreground p-4 pt-1 text-xs"> Bolt seals SL-94021 & SL-94022 confirmed tamper-free at origin gate inspection. </div> </div> </div> <div ui-card class="border-border bg-card"> <div ui-card-header class="p-5 pb-3"> <div class="flex items-center justify-between"> <div> <h3 ui-card-title class="text-base font-semibold"> Customs Compliance & Regulatory Audit Checklist </h3> <p ui-card-description class="text-xs"> International trade compliance checks required for European Union port entry and inland transit </p> </div> <span ui-badge wrap variant="success" class="gap-1 text-xs"> <lucide-icon [img]="CheckCircle2" class="size-3.5" /> <span>All 6 Gates Passed</span> </span> </div> </div> <div ui-card-content class="space-y-3 p-5 pt-0"> <div class="grid grid-cols-1 gap-3 md:grid-cols-2"> @for (gate of complianceGatesList; track gate.id) { <div class="border-border bg-muted/20 flex items-start gap-3 rounded-lg border p-3.5 shadow-xs"> <div class="bg-success/10 text-success mt-0.5 shrink-0 rounded-full p-1"> <lucide-icon [img]="CheckCircle2" class="size-4" /> </div> <div class="space-y-1 text-xs"> <div class="flex items-center justify-between gap-2"> <span class="text-foreground font-semibold">{{ gate.name }}</span> <span class="text-muted-foreground font-mono text-xs font-medium">{{ gate.reference }}</span> </div> <p class="text-muted-foreground leading-relaxed">{{ gate.details }}</p> <span class="text-primary block text-xs font-medium">Authority: {{ gate.authority }}</span> </div> </div> } </div> </div> </div> </div> } <!-- TAB 3: ELECTRONIC DATA INTERCHANGE (EDI) / JSON MANIFEST --> @if (activeTab() === 'edi') { <div class="space-y-4"> <div ui-card class="border-border bg-card"> <div ui-card-header class="p-5 pb-3"> <div class="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between"> <div> <h3 ui-card-title class="text-base font-semibold">Electronic Data Interchange (UN/EDIFACT IFTMIN)</h3> <p ui-card-description class="text-xs"> Standardized electronic consignment instructions formatted per UN/EDIFACT D.01B maritime transport standards </p> </div> <button ui-button variant="outline" size="sm" class="gap-1.5 self-start text-xs sm:self-auto" (click)="handleCopyEdi()" > @if (copiedEdi()) { <lucide-icon [img]="Check" class="text-success size-3.5" /> } @else { <lucide-icon [img]="Copy" class="size-3.5" /> } <span>{{ copiedEdi() ? 'Copied EDI Payload' : 'Copy EDI Payload' }}</span> </button> </div> </div> <div ui-card-content class="p-5 pt-0"> <pre class="border-border bg-muted/40 text-foreground overflow-x-auto rounded-lg border p-4 font-mono text-xs leading-relaxed select-all" >{{ generateEdiText() }}</pre> </div> </div> </div> } </div> `, }) export class UiBillOfLadingViewerComponent { protected readonly AlertCircle = AlertCircle protected readonly Anchor = Anchor protected readonly Building2 = Building2 protected readonly Check = Check protected readonly CheckCircle2 = CheckCircle2 protected readonly Copy = Copy protected readonly Download = Download protected readonly FileText = FileText protected readonly Mail = Mail protected readonly MapPin = MapPin protected readonly Phone = Phone protected readonly Printer = Printer protected readonly ShieldCheck = ShieldCheck protected readonly Ship = Ship protected readonly cargoItemsList = cargoItems protected readonly complianceGatesList = complianceGates @Input() documentTitle = 'Straight Bill of Lading (Original)' @Input() bolNumber = '#BOL-2026-894210' @Input() carrierName = 'Maersk Line A/S' @Input() scacCode = 'MAEU' @Input() bookingReference = 'BKG-771920-EU' @Input() exportReference = 'EXP-2026-0812' @Input() issueDate = 'August 21, 2026' @Input() placeOfIssue = 'Shanghai, China' @Input() shipperName = 'Shenzhen AudioTech Precision Ltd.' @Input() shipperAddress = 'Building 4, High-Tech Industrial Park, Nanshan District, Shenzhen, Guangdong 518057, China' @Input() shipperContact = '+86 755 8892 1044' @Input() shipperEmail = 'export@audiotech-cn.com' @Input() shipperEori = 'CN-91440300MA5EXP01' @Input() consigneeName = 'SonicAura Europe B.V.' @Input() consigneeAddress = 'Keizersgracht 421, 1016 EK Amsterdam, Netherlands' @Input() consigneeContact = '+31 20 894 3300' @Input() consigneeEmail = 'import@sonicaura.eu' @Input() consigneeEori = 'NL864219034 / VAT: NL864219034B01' @Input() notifyPartyName = 'Rotterdam Intermodal Logistics GmbH' @Input() notifyPartyAddress = 'Willemskade 18, 3016 DK Rotterdam, Netherlands' @Input() notifyPartyContact = '+31 10 400 9820' @Input() notifyPartyEmail = 'clearance@rotterdam-intermodal.com' @Input() notifyPartyAttention = 'Customs Brokerage Dept. / Desk 4B' @Input() preCarriageBy = 'Feeder Barge "Yangtze River 12"' @Input() placeOfReceipt = 'Shenzhen Inland Depot, China (CNSZX)' @Input() vesselVoyage = 'Ever Given / V.042W' @Input() vesselImo = 'IMO 9811000' @Input() vesselFlag = 'Panama (PA)' @Input() portOfLoading = 'Shanghai Port, China (CNSHA)' @Input() portOfDischarge = 'Rotterdam Port, Netherlands (NLRTM)' @Input() placeOfDelivery = 'Duisburg Intermodal Terminal, Germany (DEDUI)' @Input() finalDestination = 'Duisburg Central DC #4 (DEDUI)' @Input() freightTerms = 'Prepaid' @Input() freightPayableAt = 'Shanghai, China' @Input() declaredValue = '$142,500.00 USD' @Input() insuranceCert = '#INS-9421-MAR' @Input() incoterms = 'CIF Rotterdam Port (Incoterms 2020)' @Input() letterOfCredit = 'LC-BNP-2026-99410-EU (BNP Paribas)' @Input() blockchainTxHash = '0x71e4d3a8904f84c8a5142d131fba827b5e4c91a2' @Input() blockchainBlock = '59,182,904' @Input() blockchainNetwork = 'Polygon Mainnet (Decentralized Cargo Registry)' @Input() set initialTab(v: BillOfLadingTab) { this.activeTab.set(v ?? 'document') } @Input('class') className?: string @Output() readonly downloadPdf = new EventEmitter<void>() @Output() readonly verifyBlockchain = new EventEmitter<void>() @Output() readonly print = new EventEmitter<void>() readonly activeTab = signal<BillOfLadingTab>('document') readonly copiedBol = signal(false) readonly copiedEdi = signal(false) readonly showVerifyDetails = signal(false) private copyTimer: ReturnType<typeof setTimeout> | undefined constructor() { inject(DestroyRef).onDestroy(() => clearTimeout(this.copyTimer)) } readonly totalPackagesCount = computed(() => cargoItems.reduce((acc, item) => acc + item.packagesCount, 0)) readonly totalGrossWeightKg = computed(() => cargoItems.reduce((acc, item) => acc + item.grossWeightKg, 0)) readonly totalGrossWeightLbs = computed(() => cargoItems.reduce((acc, item) => acc + item.grossWeightLbs, 0)) readonly totalVolumeCbm = computed(() => cargoItems.reduce((acc, item) => acc + item.volumeCbm, 0)) get rootClass(): string { return cn('text-foreground w-full space-y-6', this.className) } tabClass(tab: BillOfLadingTab, withIcon = false): string { return cn( 'focus-visible:ring-ring rounded-md px-3 py-1.5 text-xs font-medium transition-colors focus-visible:ring-2 focus-visible:outline-none', withIcon && 'flex items-center gap-1.5', this.activeTab() === tab ? 'bg-background text-foreground shadow-xs' : 'text-muted-foreground hover:text-foreground', ) } generateEdiText(): string { return `UNB+UNOA:2+${this.scacCode}:ZZ+SONICAURA:ZZ+260821:1400+0000001' UNH+1+IFTMIN:D:01B:UN:EAN004' BGM+705+${this.bolNumber.replace('#', '')}+9' DTM+137:20260821:102' NAD+CZ+${this.shipperEori}::9++${this.shipperName}+${this.shipperAddress}+SHENZHEN++518057+CN' NAD+CN+NL864219034::9++${this.consigneeName}+${this.consigneeAddress}+AMSTERDAM++1016EK+NL' NAD+NI+++${this.notifyPartyName}+${this.notifyPartyAddress}+ROTTERDAM++3016DK+NL' TDT+20+V.042W+1+13+MAEU:172:20+++${this.vesselImo}::11:${this.vesselVoyage}' LOC+9+CNSHA:139:6+SHANGHAI PORT' LOC+11+NLRTM:139:6+ROTTERDAM PORT' LOC+7+DEDUI:139:6+DUISBURG TERMINAL' EQD+CN+MSKU9482014+45G1:102:5++2+5' SEL+SL94021+SH' GID+1+24:BX+PRO AUDIO HEADPHONES:85183000' MEA+WT+G+KGM:4850.00' MEA+VOL+AAW+MTQ:32.40' MOA+39:142500:USD' UNT+18+1' UNZ+1+0000001'` } handleCopyBol(): void { if (typeof navigator !== 'undefined' && navigator.clipboard) { void navigator.clipboard.writeText(this.bolNumber) this.copiedBol.set(true) clearTimeout(this.copyTimer) this.copyTimer = setTimeout(() => this.copiedBol.set(false), 2000) } } handleCopyEdi(): void { if (typeof navigator !== 'undefined' && navigator.clipboard) { void navigator.clipboard.writeText(this.generateEdiText()) this.copiedEdi.set(true) clearTimeout(this.copyTimer) this.copyTimer = setTimeout(() => this.copiedEdi.set(false), 2000) } } handlePrint(): void { this.print.emit() if (typeof window !== 'undefined') window.print() } handleDownloadPdf(): void { this.downloadPdf.emit() this.handlePrint() } toggleBlockchainDetails(): void { this.showVerifyDetails.update((v) => !v) this.verifyBlockchain.emit() } }src/app/components/blocks/bill-of-lading-viewer/index.ts0.2 kB
export { UiBillOfLadingViewerComponent, type BillOfLadingTab, type ComplianceGate, type ContainerCargoItem, } from './bill-of-lading-viewer.component'
Raw manifest:https://uipkge.dev/r/angular/bill-of-lading-viewer.json