Backorder Split Shipment Manager
blockdashboardOrder fulfillment split shipment manager with stock triage, backorder tracking, and multi-node dispatch controls.
Installation
$pnpm dlx uipkge-ng@latest add backorder-split-shipment-manager$npx uipkge-ng@latest add backorder-split-shipment-manager$yarn dlx uipkge-ng@latest add backorder-split-shipment-manager$bunx uipkge-ng@latest add backorder-split-shipment-managerManifest:
https://uipkge.dev/r/angular/backorder-split-shipment-manager.jsonInstalls to:src/app/components/blocks/backorder-split-shipment-manager/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.
ShipmentIteminterface ShipmentItem {
id: string
sku: string
name: string
qty: number
unitPrice: number
originHub: string
status: 'ready' | 'backordered' | 'dispatched'
restockEta?: string
}SplitPackageinterface SplitPackage {
packageId: string
carrier: string
trackingNumber?: string
status: 'ready_to_ship' | 'awaiting_inventory' | 'dispatched'
items: ShipmentItem[]
}npm dependencies
Files installed (2)
src/app/components/blocks/backorder-split-shipment-manager/backorder-split-shipment-manager.component.ts11.9 kB
import { Component, Input, signal, ChangeDetectionStrategy } from '@angular/core' import { ArrowRightLeft, CheckCircle2, Clock, LucideAngularModule, Send, Split, Truck } from 'lucide-angular' import { cn } from '@/lib/utils' import { UiBadgeComponent, type BadgeVariant } from '@/ui/badge/badge.component' import { UiButtonComponent } from '@/ui/button/button.component' import { UiCardComponent, UiCardContentComponent, UiCardDescriptionComponent, UiCardHeaderComponent, UiCardTitleComponent, } from '@/ui/card/card.component' import { UiTableBodyComponent, UiTableCellComponent, UiTableComponent, UiTableHeadComponent, UiTableHeaderComponent, UiTableRowComponent, } from '@/ui/table/table.component' export interface ShipmentItem { id: string sku: string name: string qty: number unitPrice: number originHub: string status: 'ready' | 'backordered' | 'dispatched' restockEta?: string } export interface SplitPackage { packageId: string carrier: string trackingNumber?: string status: 'ready_to_ship' | 'awaiting_inventory' | 'dispatched' items: ShipmentItem[] } export interface BackorderSplitShipmentProps { orderId?: string customerName?: string customerTier?: string className?: string } /** * Angular port of the React BackorderSplitShipmentManager block: an order summary card * (fulfillment rule, ready and backordered value) above one card per split package, each * with its carrier, a dispatch / re-route action and a line-item table. Dispatching a * ready package marks it and its items as dispatched. Edit the sample packages below. */ @Component({ changeDetection: ChangeDetectionStrategy.Eager, selector: 'ui-backorder-split-shipment-manager, [ui-backorder-split-shipment-manager]', standalone: true, host: { '[attr.data-slot]': '"backorder-split-shipment-manager"', '[class]': 'hostClass', }, imports: [ LucideAngularModule, UiBadgeComponent, UiButtonComponent, UiCardComponent, UiCardContentComponent, UiCardDescriptionComponent, UiCardHeaderComponent, UiCardTitleComponent, UiTableBodyComponent, UiTableCellComponent, UiTableComponent, UiTableHeadComponent, UiTableHeaderComponent, UiTableRowComponent, ], template: ` <!-- Header Summary Card --> <div ui-card> <div ui-card-header class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between"> <div class="space-y-1"> <div class="flex items-center gap-2"> <span class="text-foreground font-mono text-sm font-semibold">{{ orderId }}</span> <span ui-badge variant="outline">{{ customerTier }}</span> <span ui-badge variant="secondary" class="gap-1"> <lucide-icon [img]="Split" class="text-foreground size-3" /> Split Fulfillment (2 Shipments) </span> </div> <h3 ui-card-title class="text-xl">Split Shipment & Backorder Triage</h3> <p ui-card-description> Account: <strong class="text-foreground font-medium">{{ customerName }}</strong> · Multi-Node Routing </p> </div> <div class="flex items-center gap-2"> <button ui-button variant="outline" size="sm" class="gap-1.5"> <lucide-icon [img]="Clock" class="size-3.5" /> Hold Entire Order </button> <button ui-button variant="default" size="sm" class="gap-1.5"> <lucide-icon [img]="Send" class="size-3.5" /> Release Ready Packages </button> </div> </div> <div ui-card-content class="border-border grid grid-cols-1 gap-4 border-t pt-4 sm:grid-cols-3"> <!-- Fulfillment Policy --> <div class="border-border space-y-1 rounded-lg border p-3"> <div class="text-muted-foreground text-xs font-medium">FULFILLMENT RULE</div> <div class="text-foreground text-sm font-semibold">Partial Dispatch Allowed (Speed Priority)</div> <div class="text-muted-foreground text-xs">Ship available items immediately without backorder delay.</div> </div> <!-- In-Stock Value --> <div class="border-border space-y-1 rounded-lg border p-3"> <div class="text-muted-foreground text-xs font-medium">READY SHIPMENT VALUE</div> <div class="text-foreground font-mono text-xl font-semibold">$630.00 USD</div> <div class="text-muted-foreground text-xs">6 units allocated from East Coast Hub.</div> </div> <!-- Backordered Value --> <div class="border-border space-y-1 rounded-lg border p-3"> <div class="text-muted-foreground text-xs font-medium">BACKORDERED VALUE</div> <div class="text-foreground font-mono text-xl font-semibold">$420.00 USD</div> <div class="text-muted-foreground text-xs">1 unit waiting on PO-2026-881 inbound dock.</div> </div> </div> </div> <!-- Split Packages Breakdown --> <div class="space-y-4"> @for (pkg of packages(); track pkg.packageId) { <div ui-card> <div ui-card-header class="flex flex-col gap-3 pb-3 sm:flex-row sm:items-center sm:justify-between"> <div class="space-y-1"> <div class="flex items-center gap-2"> <span class="text-foreground font-semibold">{{ pkg.packageId }}</span> <span ui-badge [variant]="statusVariant(pkg.status)" class="capitalize">{{ statusLabel(pkg.status) }}</span> </div> <!-- React renders the tracking span with no space after the carrier (JSX strips the newline). --> <div class="text-muted-foreground font-mono text-xs"> Carrier: {{ pkg.carrier }}@if (pkg.trackingNumber) { <span>· Tracking: {{ pkg.trackingNumber }}</span> } </div> </div> <div class="flex items-center gap-2"> @if (pkg.status === 'ready_to_ship') { <button ui-button size="sm" variant="default" class="gap-1.5" (click)="dispatchPackage(pkg.packageId)"> <lucide-icon [img]="Truck" class="size-3.5" /> Dispatch Package Now </button> } @if (pkg.status === 'awaiting_inventory') { <button ui-button size="sm" variant="outline" class="gap-1.5"> <lucide-icon [img]="ArrowRightLeft" class="size-3.5" /> Re-route to 3PL </button> } @if (pkg.status === 'dispatched') { <span ui-badge variant="outline" class="text-xs">Dispatched</span> } </div> </div> <div ui-card-content class="p-0"> <div ui-table> <thead ui-table-header> <tr ui-table-row> <th ui-table-head class="w-[300px]">SKU & Description</th> <th ui-table-head>Fulfillment Origin</th> <th ui-table-head class="text-right">Qty</th> <th ui-table-head class="text-right">Unit Price</th> <th ui-table-head class="text-right">Line Total</th> <th ui-table-head>Inventory Status / ETA</th> </tr> </thead> <tbody ui-table-body> @for (item of pkg.items; track item.id) { <tr ui-table-row> <td ui-table-cell> <div class="text-foreground font-medium">{{ item.name }}</div> <div class="text-muted-foreground font-mono text-xs">{{ item.sku }}</div> </td> <td ui-table-cell> <span class="bg-muted text-foreground rounded px-2 py-0.5 font-mono text-xs font-medium">{{ item.originHub }}</span> </td> <td ui-table-cell class="text-right font-mono text-sm">{{ item.qty }}</td> <td ui-table-cell class="text-muted-foreground text-right font-mono text-sm"> {{ money(item.unitPrice) }} </td> <td ui-table-cell class="text-right font-mono text-sm font-medium"> {{ money(item.qty * item.unitPrice) }} </td> <td ui-table-cell> @if (item.status === 'ready') { <div class="text-foreground flex items-center gap-1.5 text-xs"> <lucide-icon [img]="CheckCircle2" class="size-3.5" /> <span>Allocated & Packed</span> </div> } @if (item.status === 'dispatched') { <div class="text-foreground flex items-center gap-1.5 text-xs"> <lucide-icon [img]="Truck" class="size-3.5" /> <span>In Transit</span> </div> } @if (item.status === 'backordered') { <div class="text-muted-foreground flex items-center gap-1.5 text-xs"> <lucide-icon [img]="Clock" class="size-3.5" /> <span>{{ item.restockEta }}</span> </div> } </td> </tr> } </tbody> </div> </div> </div> } </div> `, }) export class UiBackorderSplitShipmentManagerComponent { @Input() orderId = 'ORD-SPLIT-9921' @Input() customerName = 'Aero Dynamics Corp' @Input() customerTier = 'Enterprise VIP · Net-30' @Input('class') className?: string protected readonly ArrowRightLeft = ArrowRightLeft protected readonly CheckCircle2 = CheckCircle2 protected readonly Clock = Clock protected readonly Send = Send protected readonly Split = Split protected readonly Truck = Truck readonly packages = signal<SplitPackage[]>([ { packageId: 'PKG-A (In Stock · WH-East)', carrier: 'FedEx Priority Overnight', trackingNumber: 'FX-8891-4401-US', status: 'ready_to_ship', items: [ { id: 'pkg-1-1', sku: 'SKU-SERVO-01', name: 'Precision Micro Servo Motor 12V', qty: 4, unitPrice: 125, originHub: 'wh-east-01', status: 'ready', }, { id: 'pkg-1-2', sku: 'SKU-FLANGE-99', name: 'Anodized Billet Flange Mount 45mm', qty: 2, unitPrice: 65, originHub: 'wh-east-01', status: 'ready', }, ], }, { packageId: 'PKG-B (Backordered · Sourcing WH-West)', carrier: 'UPS Standard Ground', status: 'awaiting_inventory', items: [ { id: 'pkg-2-1', sku: 'SKU-CTRL-BOARD', name: 'Industrial Embedded Controller Rev 3.2', qty: 1, unitPrice: 420, originHub: 'wh-west-02', status: 'backordered', restockEta: 'Inbound PO arrives in 2 business days', }, ], }, ]) get hostClass(): string { return cn('block w-full space-y-6', this.className) } dispatchPackage(pkgId: string): void { this.packages.update((prev) => prev.map((pkg) => pkg.packageId === pkgId ? { ...pkg, status: 'dispatched' as const, items: pkg.items.map((i) => ({ ...i, status: 'dispatched' as const })), } : pkg, ), ) } protected statusVariant(status: SplitPackage['status']): BadgeVariant { return status === 'dispatched' ? 'default' : status === 'ready_to_ship' ? 'secondary' : 'outline' } protected statusLabel(status: SplitPackage['status']): string { return status.replace(/_/g, ' ') } protected money(value: number): string { return `$${value.toFixed(2)}` } }src/app/components/blocks/backorder-split-shipment-manager/index.ts0.2 kB
export { UiBackorderSplitShipmentManagerComponent } from './backorder-split-shipment-manager.component' export type { ShipmentItem, SplitPackage, BackorderSplitShipmentProps, } from './backorder-split-shipment-manager.component'
Raw manifest:https://uipkge.dev/r/angular/backorder-split-shipment-manager.json