Packing Slip Generator
blocklogisticsShopify and ShipStation style warehouse order packing slip and picker verification checklist with warehouse header, customer ship-to routing, picker pack station barcode scan identifier, itemized pick list table with interactive packed checkboxes, bin location tags, SKU barcodes, weight calculations, and quality control inspector stamp.
Installation
$pnpm dlx uipkge-ng@latest add packing-slip-generator$npx uipkge-ng@latest add packing-slip-generator$yarn dlx uipkge-ng@latest add packing-slip-generator$bunx uipkge-ng@latest add packing-slip-generatorManifest:
https://uipkge.dev/r/angular/packing-slip-generator.jsonInstalls to:src/app/components/blocks/packing-slip-generator/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.
PackingSlipIteminterface PackingSlipItem {
id: string
sku: string
barcode: string
name: string
variant: string
location: string
qtyOrdered: number
qtyPacked: number
weightLbs: number
notes?: string
initialPacked?: boolean
}PackingSlipGeneratorInputsinterface PackingSlipGeneratorInputs {
packingSlipNo?: string
orderNo?: string
carrier?: string
carrierService?: string
trackingNo?: string
orderDate?: string
shipDate?: string
warehouseName?: string
warehouseFacility?: string
warehouseAddress?: string
warehousePhone?: string
warehouseEmail?: string
customerName?: string
customerCompany?: string
customerAddress?: string
customerPhone?: string
customerEmail?: string
deliveryNotes?: string
pickerName?: string
pickerId?: string
packStation?: string
shift?: string
boxSize?: string
dunnageType?: string
qcInspector?: string
qcStamp?: string
qcTimestamp?: string
items?: PackingSlipItem[]
showActions?: boolean
initialPackedAll?: boolean
}Files installed (2)
src/app/components/blocks/packing-slip-generator/packing-slip-generator.component.ts36.1 kB
// Shopify / ShipStation style warehouse order packing slip with an interactive picker // verification checklist: warehouse header, ship-to / origin / carrier routing grid, picker // pack-station ribbon with a simulated Code-128 barcode, itemized pick list table with packed // checkboxes, carton specs, returns notice and a QC inspector stamp. Port of the React block 1:1. import { ChangeDetectionStrategy, Component, Input, computed, signal } from '@angular/core' import { Box, Building2, Check, CheckCheck, CheckCircle2, Clock, Copy, FileDown, LucideAngularModule, MapPin, Printer, QrCode, RotateCcw, Scan, ShieldCheck, Tag, Truck, User, Warehouse, Zap, } from 'lucide-angular' import { cn } from '@/lib/utils' import { UiBadgeComponent } from '@/ui/badge/badge.component' import { UiButtonComponent } from '@/ui/button/button.component' import { UiCheckboxComponent } from '@/ui/checkbox/checkbox.component' import { UiSeparatorComponent } from '@/ui/separator/separator.component' import { UiTableBodyComponent, UiTableCellComponent, UiTableComponent, UiTableFooterComponent, UiTableHeadComponent, UiTableHeaderComponent, UiTableRowComponent, } from '@/ui/table/table.component' export interface PackingSlipItem { id: string sku: string barcode: string name: string variant: string location: string qtyOrdered: number qtyPacked: number weightLbs: number notes?: string initialPacked?: boolean } export interface PackingSlipGeneratorInputs { packingSlipNo?: string orderNo?: string carrier?: string carrierService?: string trackingNo?: string orderDate?: string shipDate?: string warehouseName?: string warehouseFacility?: string warehouseAddress?: string warehousePhone?: string warehouseEmail?: string customerName?: string customerCompany?: string customerAddress?: string customerPhone?: string customerEmail?: string deliveryNotes?: string pickerName?: string pickerId?: string packStation?: string shift?: string boxSize?: string dunnageType?: string qcInspector?: string qcStamp?: string qcTimestamp?: string items?: PackingSlipItem[] showActions?: boolean initialPackedAll?: boolean } const defaultItems: PackingSlipItem[] = [ { id: 'item-1', sku: '#SKU-84920', barcode: '849201928410', name: 'Aero Minimalist Runner', variant: 'Size 10.5 · Matte Black', location: 'Aisle 04 · Rack B · Shelf 02', qtyOrdered: 1, qtyPacked: 1, weightLbs: 1.8, notes: 'Eco-Knit Upper / EVA Cushioning Sole', initialPacked: true, }, { id: 'item-2', sku: '#SKU-77219', barcode: '772198421093', name: 'HydroShield Waterproof Shell Jacket', variant: "Men's M · Alpine Green", location: 'Aisle 02 · Rack D · Shelf 01', qtyOrdered: 1, qtyPacked: 1, weightLbs: 1.6, notes: '3-Layer DWR Membrane / Sealed Seams', initialPacked: true, }, { id: 'item-3', sku: '#SKU-31904', barcode: '319045812903', name: 'Ergonomic Trail Crew Socks (3-Pack)', variant: 'Charcoal / Grey · L', location: 'Aisle 08 · Rack A · Shelf 04', qtyOrdered: 1, qtyPacked: 1, weightLbs: 1.4, notes: 'Merino Wool Blend / Seamless Toe', initialPacked: false, }, ] function buildPackedMap(items: PackingSlipItem[], packedAll: boolean): Record<string, boolean> { const map: Record<string, boolean> = {} for (const item of items) map[item.id] = packedAll ? true : (item.initialPacked ?? false) return map } @Component({ changeDetection: ChangeDetectionStrategy.Eager, selector: 'ui-packing-slip-generator, [ui-packing-slip-generator]', 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, UiCheckboxComponent, UiSeparatorComponent, UiTableBodyComponent, UiTableCellComponent, UiTableComponent, UiTableFooterComponent, UiTableHeadComponent, UiTableHeaderComponent, UiTableRowComponent, ], template: ` <div data-slot="packing-slip-generator" [class]="rootClass"> <!-- Top Action & Fulfillment Toolbar (Screen Only) --> @if (showActions()) { <div class="no-print 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="flex flex-wrap items-center gap-3"> <div class="flex items-center gap-2"> <span class="text-foreground font-mono text-sm font-semibold tracking-tight">{{ packingSlipNo() }}</span> <span ui-badge variant="outline" class="gap-1 font-mono text-xs"> <lucide-icon [img]="Tag" class="text-muted-foreground size-3" /> {{ orderNo() }} </span> </div> <ui-separator orientation="vertical" class="hidden h-4 sm:block" /> <div class="text-muted-foreground flex items-center gap-1.5 text-xs"> <lucide-icon [img]="Truck" class="text-primary size-3.5" /> <span class="text-foreground font-medium">{{ carrierService() }}</span> </div> <span ui-badge [variant]="isAllPacked() ? 'default' : 'secondary'" class="gap-1 text-xs font-medium"> @if (isAllPacked()) { <lucide-icon [img]="CheckCircle2" class="size-3" /> } @else { <lucide-icon [img]="Clock" class="size-3" /> } {{ isAllPacked() ? 'All Items Verified' : verifiedItemsCount() + '/' + effectiveItems().length + ' Packed (' + progressPercentage() + '%)' }} </span> </div> <div class="flex flex-wrap items-center gap-2"> <button ui-button type="button" variant="outline" size="sm" class="gap-1.5 text-xs font-medium" (click)="isAllPacked() ? resetPackingChecklist() : markAllPacked()" > @if (isAllPacked()) { <lucide-icon [img]="RotateCcw" class="size-3.5" /> } @else { <lucide-icon [img]="CheckCheck" class="size-3.5" /> } {{ isAllPacked() ? 'Reset Checklist' : 'Verify All' }} </button> <button ui-button aria-label="Download attachment" type="button" variant="outline" size="sm" class="gap-1.5 text-xs font-medium" [disabled]="isDownloading()" (click)="handleDownloadPdf()" > <lucide-icon [img]="FileDown" class="size-3.5" /> {{ isDownloading() ? 'Generating PDF...' : 'Download PDF' }} </button> <button ui-button type="button" size="sm" class="gap-1.5 text-xs font-medium" (click)="handlePrint()"> <lucide-icon [img]="Printer" class="size-3.5" /> Print 4x6 Slip </button> </div> </div> } <!-- Printable Packing Slip Paper Container --> <div class="packing-slip-paper border-border bg-card mx-auto w-full max-w-4xl space-y-6 rounded-xl border p-6 shadow-md transition-shadow sm:p-8" > <!-- Section 1: Warehouse & Slip Header --> <div class="flex flex-col gap-6 sm:flex-row sm:items-start sm:justify-between"> <div class="space-y-2"> <div class="flex items-center gap-2.5"> <div class="bg-primary text-primary-foreground flex size-10 items-center justify-center rounded-lg shadow-xs" > <lucide-icon [img]="Warehouse" class="size-5" /> </div> <div> <h2 class="text-foreground text-base font-semibold tracking-tight sm:text-lg"> {{ warehouseName() }} </h2> <p class="text-muted-foreground text-xs">{{ warehouseFacility() }}</p> </div> </div> <div class="text-muted-foreground text-xs"> <p>{{ warehouseAddress() }}</p> <p class="mt-0.5">{{ warehousePhone() }} · {{ warehouseEmail() }}</p> </div> </div> <div class="flex flex-col items-start gap-3 sm:items-end"> <div class="border-primary/20 bg-primary/5 text-primary inline-flex items-center rounded-md border px-2.5 py-1 text-xs font-semibold" > COMMERCIAL PACKING SLIP </div> <div class="space-y-1 text-left text-xs sm:text-right"> <div class="flex items-center gap-2 sm:justify-end"> <span class="text-muted-foreground">Packing Slip Ref:</span> <span class="text-foreground font-mono font-semibold">{{ packingSlipNo() }}</span> </div> <div class="flex items-center gap-2 sm:justify-end"> <span class="text-muted-foreground">Order Reference:</span> <span class="text-foreground font-mono font-semibold">{{ orderNo() }}</span> </div> <div class="flex items-center gap-2 sm:justify-end"> <span class="text-muted-foreground">Order Date:</span> <span class="text-foreground font-medium">{{ orderDate() }}</span> </div> <div class="flex items-center gap-2 sm:justify-end"> <span class="text-muted-foreground">Ship Date:</span> <span class="text-foreground font-medium">{{ shipDate() }}</span> </div> </div> </div> </div> <ui-separator /> <!-- Section 2: Routing Grid (Ship-To, Origin, Carrier) --> <div class="grid grid-cols-1 gap-4 md:grid-cols-3"> <div class="border-border/80 bg-muted/20 rounded-lg border p-4"> <div class="text-muted-foreground mb-2.5 flex items-center gap-2 text-xs font-medium"> <lucide-icon [img]="User" class="text-primary size-3.5" /> Ship-To Recipient </div> <div class="space-y-1 text-xs"> <p class="text-foreground text-sm font-semibold">{{ customerName() }}</p> <p class="text-foreground/80 font-medium">{{ customerCompany() }}</p> <p class="text-muted-foreground">{{ customerAddress() }}</p> <p class="text-muted-foreground">{{ customerPhone() }}</p> @if (deliveryNotes()) { <div class="border-warning/20 bg-warning/10 text-warning mt-2.5 rounded border p-2"> <p class="text-xs font-semibold">Delivery Note:</p> <p class="text-xs">{{ deliveryNotes() }}</p> </div> } </div> </div> <div class="border-border/80 bg-muted/20 rounded-lg border p-4"> <div class="text-muted-foreground mb-2.5 flex items-center gap-2 text-xs font-medium"> <lucide-icon [img]="Building2" class="text-primary size-3.5" /> Fulfillment Origin </div> <div class="space-y-1 text-xs"> <p class="text-foreground text-sm font-semibold">{{ warehouseName() }}</p> <p class="text-foreground/80 font-medium">Outbound Logistics Dock #12</p> <p class="text-muted-foreground">{{ warehouseAddress() }}</p> <p class="text-muted-foreground">Support: {{ warehousePhone() }}</p> <div class="text-muted-foreground mt-2.5 flex items-center gap-1.5 text-xs"> <lucide-icon [img]="Zap" class="text-success size-3" /> <span>Direct EDI Dispatch Cleared</span> </div> </div> </div> <div class="border-border/80 bg-muted/20 rounded-lg border p-4"> <div class="text-muted-foreground mb-2.5 flex items-center gap-2 text-xs font-medium"> <lucide-icon [img]="Truck" class="text-primary size-3.5" /> Carrier & Service </div> <div class="space-y-2 text-xs"> <div> <p class="text-foreground text-sm font-semibold">{{ carrierService() }}</p> <p class="text-muted-foreground">Standard Air Parcel · Guaranteed</p> </div> <div> <span class="text-muted-foreground">Tracking Number:</span> <div class="mt-1 flex items-center gap-2"> <span class="text-foreground font-mono text-xs font-semibold">{{ trackingNo() }}</span> <button type="button" class="no-print border-border hover:bg-muted focus-visible:ring-ring inline-flex size-6 items-center justify-center rounded border focus-visible:ring-2 focus-visible:outline-none" title="Copy Tracking Number" (click)="copyTrackingNumber()" > @if (copiedTracking()) { <lucide-icon [img]="Check" class="text-muted-foreground size-3" /> } @else { <lucide-icon [img]="Copy" class="text-muted-foreground size-3" /> } </button> </div> </div> <div class="text-muted-foreground border-border/40 flex items-center justify-between border-t pt-1 text-xs" > <span>Billing: Prepaid</span> <span class="font-mono">Declared: $349.00</span> </div> </div> </div> </div> <!-- Section 3: Picker & Pack Station Ribbon --> <div class="border-border bg-muted/40 flex flex-col gap-4 rounded-lg border p-4 sm:flex-row sm:items-center sm:justify-between" > <div class="flex flex-wrap items-center gap-4"> <div class="space-y-0.5"> <div class="text-muted-foreground flex items-center gap-1.5 text-xs font-medium"> <lucide-icon [img]="User" class="text-primary size-3.5" /> Picker & Station </div> <p class="text-foreground text-sm font-semibold"> {{ pickerName() }} <span class="text-muted-foreground font-mono text-xs font-normal">({{ pickerId() }})</span> </p> <p class="text-muted-foreground text-xs">{{ packStation() }} · {{ shift() }}</p> </div> <ui-separator orientation="vertical" class="hidden h-10 md:block" /> <div class="space-y-0.5"> <div class="text-muted-foreground flex items-center gap-1.5 text-xs font-medium"> <lucide-icon [img]="Scan" class="text-primary size-3.5" /> Packing Progress </div> <div class="flex items-center gap-2"> <div class="bg-muted border-border/50 h-2 w-28 overflow-hidden rounded-full border"> <div class="bg-primary h-full transition-[width] duration-300" [style.width.%]="progressPercentage()" ></div> </div> <span class="text-foreground font-mono text-xs font-semibold tabular-nums"> {{ verifiedItemsCount() }}/{{ effectiveItems().length }} Verified </span> </div> </div> </div> <div class="border-border/60 bg-background/80 flex flex-col items-center justify-center rounded border px-3 py-2 text-center shadow-2xs sm:items-end" > <div class="flex h-8 items-center gap-[2px]" aria-label="Barcode"> <span class="bg-foreground h-8 w-[3px]"></span> <span class="h-8 w-[1px] bg-transparent"></span> <span class="bg-foreground h-8 w-[2px]"></span> <span class="h-8 w-[1px] bg-transparent"></span> <span class="bg-foreground h-8 w-[4px]"></span> <span class="h-8 w-[2px] bg-transparent"></span> <span class="bg-foreground h-8 w-[1px]"></span> <span class="bg-foreground h-8 w-[3px]"></span> <span class="h-8 w-[1px] bg-transparent"></span> <span class="bg-foreground h-8 w-[5px]"></span> <span class="h-8 w-[2px] bg-transparent"></span> <span class="bg-foreground h-8 w-[2px]"></span> <span class="h-8 w-[1px] bg-transparent"></span> <span class="bg-foreground h-8 w-[4px]"></span> <span class="h-8 w-[2px] bg-transparent"></span> <span class="bg-foreground h-8 w-[3px]"></span> <span class="h-8 w-[1px] bg-transparent"></span> <span class="bg-foreground h-8 w-[5px]"></span> <span class="h-8 w-[2px] bg-transparent"></span> <span class="bg-foreground h-8 w-[2px]"></span> <span class="h-8 w-[1px] bg-transparent"></span> <span class="bg-foreground h-8 w-[3px]"></span> <span class="h-8 w-[1px] bg-transparent"></span> <span class="bg-foreground h-8 w-[4px]"></span> <span class="h-8 w-[2px] bg-transparent"></span> <span class="bg-foreground h-8 w-[5px]"></span> <span class="h-8 w-[1px] bg-transparent"></span> <span class="bg-foreground h-8 w-[3px]"></span> <span class="h-8 w-[2px] bg-transparent"></span> <span class="bg-foreground h-8 w-[4px]"></span> <span class="h-8 w-[1px] bg-transparent"></span> <span class="bg-foreground h-8 w-[2px]"></span> <span class="h-8 w-[2px] bg-transparent"></span> <span class="bg-foreground h-8 w-[5px]"></span> <span class="h-8 w-[1px] bg-transparent"></span> <span class="bg-foreground h-8 w-[3px]"></span> </div> <p class="text-foreground font-mono text-xs font-semibold tracking-widest">*{{ barcodeRef() }}*</p> </div> </div> <!-- Section 4: Itemized Pick List Table --> <div class="space-y-2"> <div class="flex items-center justify-between"> <div class="flex items-center gap-2"> <h3 class="text-foreground text-sm font-semibold tracking-tight">Itemized Pick & Pack Checklist</h3> <span ui-badge variant="outline" class="font-mono text-xs"> {{ effectiveItems().length }} Line Items </span> </div> <span class="text-muted-foreground text-xs">Check items upon bin scan and physical carton packing</span> </div> <div class="border-border overflow-hidden rounded-lg border"> <div class="overflow-x-auto"> <table ui-table> <thead ui-table-header class="bg-muted/40"> <tr ui-table-row> <th ui-table-head class="w-12 text-center">Packed</th> <th ui-table-head class="w-48">Bin / Location</th> <th ui-table-head class="w-36">SKU & Barcode</th> <th ui-table-head>Item Name & Variant Details</th> <th ui-table-head class="w-28 text-center">Qty Pick/Ord</th> <th ui-table-head class="w-24 text-right">Weight</th> </tr> </thead> <tbody ui-table-body> @for (item of effectiveItems(); track item.id) { <tr ui-table-row [class]="rowClass(item.id)" (click)="toggleItemPacked(item.id)"> <td ui-table-cell class="text-center" (click)="$event.stopPropagation()"> <div class="flex items-center justify-center"> <ui-checkbox [id]="'chk-' + item.id" [checked]="!!packedState()[item.id]" (checkedChange)="setItemPacked(item.id, $event)" /> </div> </td> <td ui-table-cell> <div class="border-border bg-muted/60 text-foreground inline-flex items-center gap-1.5 rounded-md border px-2 py-1 text-xs font-medium" > <lucide-icon [img]="MapPin" class="text-primary size-3 shrink-0" /> <span>{{ item.location }}</span> </div> </td> <td ui-table-cell> <div class="space-y-0.5"> <p class="text-foreground font-mono text-xs font-semibold">{{ item.sku }}</p> <p class="text-muted-foreground font-mono text-xs">UPC: {{ item.barcode }}</p> </div> </td> <td ui-table-cell> <div class="space-y-0.5"> <p [class]="itemNameClass(item.id)">{{ item.name }}</p> <p class="text-muted-foreground text-xs">{{ item.variant }}</p> @if (item.notes) { <p class="text-muted-foreground/80 text-xs italic">{{ item.notes }}</p> } </div> </td> <td ui-table-cell class="text-center"> <div class="border-border/80 bg-background text-foreground inline-flex items-center gap-1 rounded border px-2 py-0.5 font-mono text-xs font-semibold shadow-2xs" > <span [class]="packedState()[item.id] ? 'text-primary' : 'text-foreground'">{{ packedState()[item.id] ? item.qtyPacked : 0 }}</span> <span class="text-muted-foreground font-normal">/</span> <span class="tabular-nums">{{ item.qtyOrdered }}</span> </div> </td> <td ui-table-cell class="text-right"> <span class="text-foreground font-mono text-xs font-medium tabular-nums"> {{ item.weightLbs.toFixed(1) }} lbs </span> </td> </tr> } </tbody> <tfoot ui-table-footer class="bg-muted/50 font-medium"> <tr ui-table-row> <td ui-table-cell colspan="4" class="text-foreground text-left text-xs font-semibold"> Order Package Totals ({{ effectiveItems().length }} Distinct SKUs) </td> <td ui-table-cell class="text-foreground text-center font-mono text-xs font-semibold tabular-nums"> {{ totalPackedUnits() }} / {{ totalOrderedUnits() }} Units </td> <td ui-table-cell class="text-foreground text-right font-mono text-xs font-semibold tabular-nums"> {{ totalWeightLabel() }} </td> </tr> </tfoot> </table> </div> </div> </div> <!-- Section 5: Package Summary & Quality Control Certification --> <div class="grid grid-cols-1 gap-4 md:grid-cols-3"> <div class="border-border/80 bg-muted/20 space-y-2 rounded-lg border p-4"> <div class="text-muted-foreground flex items-center gap-2 text-xs font-medium"> <lucide-icon [img]="Box" class="text-primary size-3.5" /> Carton Specifications </div> <div class="space-y-1.5 text-xs"> <div class="flex items-center justify-between"> <span class="text-muted-foreground">Packing Box:</span> <span class="text-foreground font-medium">{{ boxSize() }}</span> </div> <div class="flex items-center justify-between"> <span class="text-muted-foreground">Total Units:</span> <span class="text-foreground font-mono font-semibold">{{ totalOrderedUnits() }} items</span> </div> <div class="flex items-center justify-between"> <span class="text-muted-foreground">Gross Weight:</span> <span class="text-foreground font-mono font-semibold">{{ totalWeightLabel() }}</span> </div> <div class="flex items-center justify-between"> <span class="text-muted-foreground">Void Fill / Dunnage:</span> <span class="text-muted-foreground">{{ dunnageType() }}</span> </div> </div> </div> <div class="border-border/80 bg-muted/20 space-y-2 rounded-lg border p-4"> <div class="text-muted-foreground flex items-center gap-2 text-xs font-medium"> <lucide-icon [img]="QrCode" class="text-primary size-3.5" /> Hassle-Free Returns </div> <div class="text-muted-foreground space-y-1 text-xs"> <p class="text-foreground font-medium">Need to make an exchange or return?</p> <p> Scan the QR code or visit <span class="text-foreground font-mono">apexretail.com/returns</span> within 30 days. </p> <p class="text-muted-foreground/90 pt-1 text-xs"> Please include this packing slip with items in original condition. </p> </div> </div> <div class="border-border/80 bg-muted/20 flex flex-col items-center justify-center rounded-lg border p-4"> <div class="border-success/70 bg-success/10 border-success/70 bg-success/15 w-full rotate-[-1.5deg] rounded-lg border-2 border-dashed p-3 text-center transition-transform hover:rotate-0" > <div class="text-success flex items-center justify-center gap-1.5"> <lucide-icon [img]="ShieldCheck" class="size-4 shrink-0" /> <span class="font-mono text-xs font-semibold tracking-widest uppercase">{{ qcStamp() }}</span> </div> <p class="text-success dark:text-foreground mt-1 font-mono text-xs font-semibold">AUDITED & SEALED</p> <p class="text-success/80 mt-0.5 text-xs">Inspector: {{ qcInspector() }}</p> <p class="text-success/70 font-mono text-xs">{{ qcTimestamp() }}</p> </div> </div> </div> <!-- Section 6: Document Footer Note --> <div class="border-border text-muted-foreground flex flex-col items-center justify-between gap-2 border-t pt-4 text-xs sm:flex-row" > <p>Generated by Apex Logistics WMS v4.8.2 · Station ID: {{ packStation() }}</p> <p class="font-mono">DOC ID: PS-2026-849201-US-04 · Barcode Validated</p> </div> </div> </div> `, }) export class UiPackingSlipGeneratorComponent { protected readonly Box = Box protected readonly Building2 = Building2 protected readonly Check = Check protected readonly CheckCheck = CheckCheck protected readonly CheckCircle2 = CheckCircle2 protected readonly Clock = Clock protected readonly Copy = Copy protected readonly FileDown = FileDown protected readonly MapPin = MapPin protected readonly Printer = Printer protected readonly QrCode = QrCode protected readonly RotateCcw = RotateCcw protected readonly Scan = Scan protected readonly ShieldCheck = ShieldCheck protected readonly Tag = Tag protected readonly Truck = Truck protected readonly User = User protected readonly Warehouse = Warehouse protected readonly Zap = Zap readonly packingSlipNo = signal('#PS-849201') readonly orderNo = signal('#ORD-92841') readonly carrier = signal('FedEx') readonly carrierService = signal('FedEx 2-Day Air') readonly trackingNo = signal('7849 2018 9284 1029') readonly orderDate = signal('Aug 21, 2026') readonly shipDate = signal('Aug 21, 2026') readonly warehouseName = signal('Apex Retail Warehouse #04') readonly warehouseFacility = signal('West Coast Omnichannel Hub') readonly warehouseAddress = signal('1040 North Industry Pkwy, Reno, NV 89502') readonly warehousePhone = signal('+1 (800) 555-0199') readonly warehouseEmail = signal('fulfillment-reno4@apexretail.io') readonly customerName = signal('Eleanor Vance') readonly customerCompany = signal('Vance Design Studios · Suite 400') readonly customerAddress = signal('742 Evergreen Terrace, Springfield, OR 97477') readonly customerPhone = signal('+1 (555) 839-2019') readonly customerEmail = signal('eleanor.vance@vancestudios.design') readonly deliveryNotes = signal('Gate Code: #4829. Leave at covered front porch if unavailable. Do not bend.') readonly pickerName = signal('Marcus Vance') readonly pickerId = signal('#EMP-4821') readonly packStation = signal('Pack Station #4') readonly shift = signal('Morning Shift · Bay Alpha') readonly boxSize = signal('Box #3 · 14" × 10" × 6"') readonly dunnageType = signal('Recycled Kraft Void Fill (1.2 oz)') readonly qcInspector = signal('Elena Rostova (#QC-89)') readonly qcStamp = signal('QC Passed · Station #4') readonly qcTimestamp = signal('2026-08-21 09:42 PST') readonly showActions = signal(true) readonly initialPackedAll = signal(false) private readonly itemsInput = signal<PackingSlipItem[] | undefined>(undefined) @Input('packingSlipNo') set packingSlipNoAttr(v: string | undefined) { if (v !== undefined) this.packingSlipNo.set(v) } @Input('orderNo') set orderNoAttr(v: string | undefined) { if (v !== undefined) this.orderNo.set(v) } @Input('carrier') set carrierAttr(v: string | undefined) { if (v !== undefined) this.carrier.set(v) } @Input('carrierService') set carrierServiceAttr(v: string | undefined) { if (v !== undefined) this.carrierService.set(v) } @Input('trackingNo') set trackingNoAttr(v: string | undefined) { if (v !== undefined) this.trackingNo.set(v) } @Input('orderDate') set orderDateAttr(v: string | undefined) { if (v !== undefined) this.orderDate.set(v) } @Input('shipDate') set shipDateAttr(v: string | undefined) { if (v !== undefined) this.shipDate.set(v) } @Input('warehouseName') set warehouseNameAttr(v: string | undefined) { if (v !== undefined) this.warehouseName.set(v) } @Input('warehouseFacility') set warehouseFacilityAttr(v: string | undefined) { if (v !== undefined) this.warehouseFacility.set(v) } @Input('warehouseAddress') set warehouseAddressAttr(v: string | undefined) { if (v !== undefined) this.warehouseAddress.set(v) } @Input('warehousePhone') set warehousePhoneAttr(v: string | undefined) { if (v !== undefined) this.warehousePhone.set(v) } @Input('warehouseEmail') set warehouseEmailAttr(v: string | undefined) { if (v !== undefined) this.warehouseEmail.set(v) } @Input('customerName') set customerNameAttr(v: string | undefined) { if (v !== undefined) this.customerName.set(v) } @Input('customerCompany') set customerCompanyAttr(v: string | undefined) { if (v !== undefined) this.customerCompany.set(v) } @Input('customerAddress') set customerAddressAttr(v: string | undefined) { if (v !== undefined) this.customerAddress.set(v) } @Input('customerPhone') set customerPhoneAttr(v: string | undefined) { if (v !== undefined) this.customerPhone.set(v) } @Input('customerEmail') set customerEmailAttr(v: string | undefined) { if (v !== undefined) this.customerEmail.set(v) } @Input('deliveryNotes') set deliveryNotesAttr(v: string | undefined) { if (v !== undefined) this.deliveryNotes.set(v) } @Input('pickerName') set pickerNameAttr(v: string | undefined) { if (v !== undefined) this.pickerName.set(v) } @Input('pickerId') set pickerIdAttr(v: string | undefined) { if (v !== undefined) this.pickerId.set(v) } @Input('packStation') set packStationAttr(v: string | undefined) { if (v !== undefined) this.packStation.set(v) } @Input('shift') set shiftAttr(v: string | undefined) { if (v !== undefined) this.shift.set(v) } @Input('boxSize') set boxSizeAttr(v: string | undefined) { if (v !== undefined) this.boxSize.set(v) } @Input('dunnageType') set dunnageAttr(v: string | undefined) { if (v !== undefined) this.dunnageType.set(v) } @Input('qcInspector') set qcInspectorAttr(v: string | undefined) { if (v !== undefined) this.qcInspector.set(v) } @Input('qcStamp') set qcStampAttr(v: string | undefined) { if (v !== undefined) this.qcStamp.set(v) } @Input('qcTimestamp') set qcTimestampAttr(v: string | undefined) { if (v !== undefined) this.qcTimestamp.set(v) } @Input('showActions') set showActionsAttr(v: boolean | undefined) { if (v !== undefined) this.showActions.set(!!v) } @Input('initialPackedAll') set initialPackedAllAttr(v: boolean | undefined) { if (v !== undefined) { this.initialPackedAll.set(!!v) this.packedState.set(buildPackedMap(this.effectiveItems(), !!v)) } } @Input('items') set itemsAttr(v: PackingSlipItem[] | undefined) { if (v !== undefined) { this.itemsInput.set([...v]) this.packedState.set(buildPackedMap(v, this.initialPackedAll())) } } @Input('class') className?: string readonly packedState = signal<Record<string, boolean>>(buildPackedMap(defaultItems, false)) readonly copiedTracking = signal(false) readonly isPrinting = signal(false) readonly isDownloading = signal(false) readonly effectiveItems = computed(() => this.itemsInput() ?? defaultItems) readonly totalOrderedUnits = computed(() => this.effectiveItems().reduce((sum, item) => sum + item.qtyOrdered, 0)) readonly totalPackedUnits = computed(() => this.effectiveItems().reduce((sum, item) => sum + (this.packedState()[item.id] ? item.qtyPacked : 0), 0), ) readonly totalWeight = computed(() => this.effectiveItems().reduce((sum, item) => sum + item.weightLbs, 0)) readonly totalWeightLabel = computed(() => `${this.totalWeight().toFixed(1)} lbs`) readonly isAllPacked = computed(() => this.effectiveItems().every((item) => this.packedState()[item.id])) readonly verifiedItemsCount = computed( () => this.effectiveItems().filter((item) => this.packedState()[item.id]).length, ) readonly progressPercentage = computed(() => { if (this.effectiveItems().length === 0) return 0 return Math.round((this.verifiedItemsCount() / this.effectiveItems().length) * 100) }) readonly barcodeRef = computed(() => this.orderNo().replace('#', '')) get rootClass(): string { return cn('w-full space-y-6', this.className) } rowClass(id: string): string { return cn( 'cursor-pointer transition-colors select-none', this.packedState()[id] ? 'bg-primary/[0.02] dark:bg-primary/[0.04]' : 'hover:bg-muted/20', ) } itemNameClass(id: string): string { return cn('text-foreground text-xs font-semibold', this.packedState()[id] && 'text-foreground/90') } toggleItemPacked(id: string): void { this.packedState.update((prev) => ({ ...prev, [id]: !prev[id] })) } setItemPacked(id: string, value: boolean | 'indeterminate'): void { this.packedState.update((prev) => ({ ...prev, [id]: value === true })) } markAllPacked(): void { const next: Record<string, boolean> = {} for (const item of this.effectiveItems()) next[item.id] = true this.packedState.set(next) } resetPackingChecklist(): void { const next: Record<string, boolean> = {} for (const item of this.effectiveItems()) next[item.id] = false this.packedState.set(next) } async copyTrackingNumber(): Promise<void> { try { await navigator.clipboard.writeText(this.trackingNo()) this.copiedTracking.set(true) setTimeout(() => this.copiedTracking.set(false), 2000) } catch { // Clipboard unavailable (non-secure context): leave the icon unchanged. } } handlePrint(): void { this.isPrinting.set(true) if (typeof window !== 'undefined') window.print() setTimeout(() => this.isPrinting.set(false), 1000) } handleDownloadPdf(): void { this.isDownloading.set(true) setTimeout(() => this.isDownloading.set(false), 1500) } }src/app/components/blocks/packing-slip-generator/index.ts0.1 kB
export { UiPackingSlipGeneratorComponent, type PackingSlipItem, type PackingSlipGeneratorInputs, } from './packing-slip-generator.component'
Raw manifest:https://uipkge.dev/r/angular/packing-slip-generator.json