Menu

Change language

GitHub repo

Backorder Split Shipment Manager

blockdashboard

Order fulfillment split shipment manager with stock triage, backorder tracking, and multi-node dispatch controls.

Installation

$npx uipkge-ng@latest add backorder-split-shipment-manager
Manifest:https://uipkge.dev/r/angular/backorder-split-shipment-manager.jsonInstalls to:src/app/components/blocks/backorder-split-shipment-manager/

Variants

Loading interactive previews…

Schema

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

ShipmentItem
interface ShipmentItem {
  id: string
  sku: string
  name: string
  qty: number
  unitPrice: number
  originHub: string
  status: 'ready' | 'backordered' | 'dispatched'
  restockEta?: string
}
SplitPackage
interface 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 &amp; 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 &amp; 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 &amp; 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

Raw manifest:https://uipkge.dev/r/angular/backorder-split-shipment-manager.json