Warehouse Inventory Grid
blocklogisticsWMS warehouse inventory and bin locator grid: facility selector, 4 summary metric cards for SKUs, capacity, low-stock triggers, and inbound shipments, searchable inventory table with multi-rack bin badges, on-hand/allocated/ATP counts, barcode scanning support, and slide-over bin relocation and receiving drawer.
Installation
$pnpm dlx uipkge-ng@latest add warehouse-inventory-grid$npx uipkge-ng@latest add warehouse-inventory-grid$yarn dlx uipkge-ng@latest add warehouse-inventory-grid$bunx uipkge-ng@latest add warehouse-inventory-gridManifest:
https://uipkge.dev/r/angular/warehouse-inventory-grid.jsonInstalls to:src/app/components/blocks/warehouse-inventory-grid/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.
WarehouseIteminterface WarehouseItem {
id: string
sku: string
barcode: string
name: string
category: string
palletId: string
zone: string
binLocation: string
aisle: string
rack: string
shelf: string
onHand: number
allocated: number
available: number
reorderThreshold: number
unitVolume: string
status: StockStatus
lastCycleCount: string
}Files installed (2)
src/app/components/blocks/warehouse-inventory-grid/warehouse-inventory-grid.component.ts42.4 kB
// WMS warehouse inventory and bin locator grid: facility selector, 4 summary metric // cards, searchable inventory table with multi-rack bin badges, on-hand/allocated/ATP // counts, barcode scanning support, and a slide-over bin relocation/receiving drawer. // Port of the React block 1:1. import { ChangeDetectionStrategy, Component, Input, computed, signal } from '@angular/core' import { AlertTriangle, ArrowDownToLine, ArrowRightLeft, ArrowUpRight, Barcode, Boxes, Building2, Check, CheckCircle2, ClipboardCheck, LucideAngularModule, MoreHorizontal, Package, PackagePlus, Printer, QrCode, Search, Tag, Truck, Warehouse, X, } 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, UiCardHeaderComponent, UiCardTitleComponent, } from '@/ui/card/card.component' import { UiDropdownMenuComponent, UiDropdownMenuContentComponent, UiDropdownMenuItemComponent, UiDropdownMenuLabelComponent, UiDropdownMenuSeparatorComponent, UiDropdownMenuTriggerComponent, } from '@/ui/dropdown-menu/dropdown-menu.component' import { UiInputComponent } from '@/ui/input/input.component' import { UiProgressComponent } from '@/ui/progress/progress.component' import { UiSelectComponent, UiSelectContentComponent, UiSelectItemComponent, UiSelectTriggerComponent, UiSelectValueComponent, } from '@/ui/select/select.component' import { UiSeparatorComponent } from '@/ui/separator/separator.component' import { UiSheetCloseComponent, UiSheetComponent, UiSheetContentComponent, UiSheetDescriptionComponent, UiSheetFooterComponent, UiSheetHeaderComponent, UiSheetTitleComponent, } from '@/ui/sheet/sheet.component' import { UiTableBodyComponent, UiTableCellComponent, UiTableComponent, UiTableHeadComponent, UiTableHeaderComponent, UiTableRowComponent, } from '@/ui/table/table.component' export type StockStatus = 'in_stock' | 'low_stock' export interface WarehouseItem { id: string sku: string barcode: string name: string category: string palletId: string zone: string binLocation: string aisle: string rack: string shelf: string onHand: number allocated: number available: number reorderThreshold: number unitVolume: string status: StockStatus lastCycleCount: string } const warehouseItems: WarehouseItem[] = [ { id: 'wh-1', sku: '#SKU-84920', barcode: 'UPC-018249', name: 'Aero Runner 10.5 Matte Black', category: 'Footwear & Apparel', palletId: 'PLT-84920-04', zone: 'Zone A-West (High-Velocity)', binLocation: 'Aisle 04 · Rack B · Shelf 02', aisle: 'Aisle 04', rack: 'Rack B', shelf: 'Shelf 02', onHand: 450, allocated: 85, available: 365, reorderThreshold: 100, unitVolume: '0.04 m³', status: 'in_stock', lastCycleCount: '2 days ago', }, { id: 'wh-2', sku: '#SKU-84921', barcode: 'UPC-029481', name: 'Technical Shell Jacket L', category: 'Outerwear', palletId: 'PLT-84921-02', zone: 'Zone A-North (Apparel Pick)', binLocation: 'Aisle 02 · Rack D · Shelf 01', aisle: 'Aisle 02', rack: 'Rack D', shelf: 'Shelf 01', onHand: 38, allocated: 30, available: 8, reorderThreshold: 50, unitVolume: '0.02 m³', status: 'low_stock', lastCycleCount: 'Yesterday', }, { id: 'wh-3', sku: '#SKU-77210', barcode: 'UPC-093812', name: 'Heavy Duty Pallet Straps (50m)', category: 'Packaging & Materials', palletId: 'PLT-77210-09', zone: 'Zone B-Bulk (Industrial)', binLocation: 'Aisle 07 · Rack A · Shelf 04', aisle: 'Aisle 07', rack: 'Rack A', shelf: 'Shelf 04', onHand: 820, allocated: 140, available: 680, reorderThreshold: 200, unitVolume: '0.08 m³', status: 'in_stock', lastCycleCount: '5 days ago', }, { id: 'wh-4', sku: '#SKU-99341', barcode: 'UPC-048201', name: 'Thermal Packaging Liner 24x24', category: 'Cold Chain Supplies', palletId: 'PLT-99341-01', zone: 'Zone C-Cold (Temp Controlled)', binLocation: 'Aisle 01 · Rack C · Shelf 03', aisle: 'Aisle 01', rack: 'Rack C', shelf: 'Shelf 03', onHand: 24, allocated: 18, available: 6, reorderThreshold: 60, unitVolume: '0.05 m³', status: 'low_stock', lastCycleCount: 'Today', }, { id: 'wh-5', sku: '#SKU-65239', barcode: 'UPC-067194', name: 'Industrial Barcode Scanner BT', category: 'WMS Equipment', palletId: 'PLT-65239-03', zone: 'Zone D-Secure (High-Value)', binLocation: 'Aisle 09 · Rack A · Shelf 01', aisle: 'Aisle 09', rack: 'Rack A', shelf: 'Shelf 01', onHand: 115, allocated: 15, available: 100, reorderThreshold: 30, unitVolume: '0.01 m³', status: 'in_stock', lastCycleCount: '3 days ago', }, { id: 'wh-6', sku: '#SKU-54018', barcode: 'UPC-081924', name: 'Anti-Static Bubble Wrap Roll', category: 'Packaging & Materials', palletId: 'PLT-54018-05', zone: 'Zone B-Bulk (Packaging Bay)', binLocation: 'Aisle 06 · Rack E · Shelf 02', aisle: 'Aisle 06', rack: 'Rack E', shelf: 'Shelf 02', onHand: 190, allocated: 45, available: 145, reorderThreshold: 75, unitVolume: '0.12 m³', status: 'in_stock', lastCycleCount: '1 week ago', }, ] @Component({ changeDetection: ChangeDetectionStrategy.Eager, selector: 'ui-warehouse-inventory-grid, [ui-warehouse-inventory-grid]', 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, UiCardHeaderComponent, UiCardTitleComponent, UiDropdownMenuComponent, UiDropdownMenuContentComponent, UiDropdownMenuItemComponent, UiDropdownMenuLabelComponent, UiDropdownMenuSeparatorComponent, UiDropdownMenuTriggerComponent, UiInputComponent, UiProgressComponent, UiSelectComponent, UiSelectContentComponent, UiSelectItemComponent, UiSelectTriggerComponent, UiSelectValueComponent, UiSeparatorComponent, UiSheetCloseComponent, UiSheetComponent, UiSheetContentComponent, UiSheetDescriptionComponent, UiSheetFooterComponent, UiSheetHeaderComponent, UiSheetTitleComponent, UiTableBodyComponent, UiTableCellComponent, UiTableComponent, UiTableHeadComponent, UiTableHeaderComponent, UiTableRowComponent, ], template: ` <div data-slot="warehouse-inventory-grid" [class]="rootClass"> <!-- Header Section --> <div class="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between"> <div class="space-y-1"> <div class="flex flex-wrap items-center gap-2.5"> <div class="bg-primary/10 text-primary flex size-8 shrink-0 items-center justify-center rounded-lg"> <lucide-icon [img]="Warehouse" class="size-4.5 shrink-0" /> </div> <h1 class="text-foreground text-xl font-bold tracking-tight sm:text-2xl"> Warehouse Inventory & Bin Locations </h1> <span ui-badge variant="outline" class="border-success/30 bg-success/10 text-success gap-1.5"> <span class="bg-success size-1.5 shrink-0 animate-pulse rounded-full"></span> WMS Live </span> </div> <p class="text-muted-foreground text-xs sm:text-sm"> Real-time multi-bin locator, safety stock reorder thresholds, and pallet relocation manifests. </p> </div> <div class="flex flex-wrap items-center gap-2.5"> <!-- Warehouse Facility Select --> <ui-select [value]="facility()" (valueChange)="facility.set($event)"> <button ui-select-trigger class="w-full sm:w-[320px]"> <lucide-icon [img]="Building2" class="text-muted-foreground size-4 shrink-0" /> <ui-select-value placeholder="Select warehouse facility" class="min-w-0 truncate" /> </button> <ui-select-content> <ui-select-item value="bay-area">Bay Area Fulfillment Center #04 · Aisle A-D</ui-select-item> <ui-select-item value="seattle">Seattle Logistics Hub #02 · High-Bay B</ui-select-item> <ui-select-item value="dallas">Dallas Central Depot #01 · Mezzanine C</ui-select-item> <ui-select-item value="newark">Newark Cold Chain #03 · Zone Free-01</ui-select-item> </ui-select-content> </ui-select> <button ui-button variant="outline" class="gap-1.5" (click)="openTransferDrawer()"> <lucide-icon [img]="ArrowRightLeft" class="size-4 shrink-0" /> Relocate Bin </button> <button ui-button class="gap-1.5 shadow-xs" (click)="openReceiveDrawer()"> <lucide-icon [img]="PackagePlus" class="size-4 shrink-0" /> Receive Inventory </button> </div> </div> <!-- Notification / Action Feedback Banner --> @if (actionFeedback()) { <div class="border-primary/20 bg-primary/5 text-foreground flex items-center justify-between rounded-lg border px-4 py-3 text-sm shadow-xs transition-colors" > <div class="flex items-center gap-2.5"> <lucide-icon [img]="CheckCircle2" class="text-success size-4 shrink-0" /> <span class="text-xs font-medium sm:text-sm">{{ actionFeedback() }}</span> </div> <button ui-button variant="ghost" size="xs" class="h-6 w-6 shrink-0 p-0" aria-label="Dismiss notification" (click)="actionFeedback.set(null)" > <lucide-icon [img]="X" class="size-3.5 shrink-0" /> </button> </div> } <!-- 4 WMS Inventory Metric Cards --> <div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4"> <!-- Metric 1: Total SKUs --> <div ui-card class="shadow-xs"> <div ui-card-header class="flex flex-row items-center justify-between space-y-0 pb-2"> <h3 ui-card-title class="text-muted-foreground text-xs font-medium tracking-wider uppercase"> Total SKUs in Stock </h3> <div class="bg-primary/10 text-primary flex size-8 shrink-0 items-center justify-center rounded-md"> <lucide-icon [img]="Package" class="size-4 shrink-0" /> </div> </div> <div ui-card-content class="space-y-1"> <div class="text-foreground text-2xl font-bold tracking-tight tabular-nums"> 1,842 <span class="text-muted-foreground text-sm font-normal">SKUs</span> </div> <p class="text-muted-foreground flex items-center gap-1.5 pt-1 text-xs"> <span class="text-success flex items-center font-medium"> <lucide-icon [img]="ArrowUpRight" class="mr-0.5 inline size-3 shrink-0" />+48 SKUs </span> <span>· 99.4% count accuracy</span> </p> </div> </div> <!-- Metric 2: Warehouse Capacity --> <div ui-card class="shadow-xs"> <div ui-card-header class="flex flex-row items-center justify-between space-y-0 pb-2"> <h3 ui-card-title class="text-muted-foreground text-xs font-medium tracking-wider uppercase"> Warehouse Capacity </h3> <div class="bg-info/10 text-info flex size-8 shrink-0 items-center justify-center rounded-md"> <lucide-icon [img]="Boxes" class="size-4 shrink-0" /> </div> </div> <div ui-card-content class="space-y-2"> <div class="flex flex-wrap items-baseline justify-between gap-x-2 gap-y-0.5"> <div class="text-foreground text-2xl font-bold tracking-tight tabular-nums">84.5%</div> <span class="text-muted-foreground text-xs tabular-nums">12,400 / 14,800 pallets</span> </div> <div ui-progress [value]="84.5" class="h-1.5"></div> <p class="text-muted-foreground text-xs">2,400 available slots in High-Bay & Bulk</p> </div> </div> <!-- Metric 3: Low Stock Alerts --> <div ui-card class="shadow-xs"> <div ui-card-header class="flex flex-row items-center justify-between space-y-0 pb-2"> <h3 ui-card-title class="text-muted-foreground text-xs font-medium tracking-wider uppercase"> Low Stock Alerts </h3> <div class="bg-warning/10 text-warning flex size-8 shrink-0 items-center justify-center rounded-md"> <lucide-icon [img]="AlertTriangle" class="size-4 shrink-0" /> </div> </div> <div ui-card-content class="space-y-1"> <div class="flex items-center gap-2"> <div class="text-warning text-2xl font-bold tracking-tight tabular-nums">12</div> <span ui-badge variant="warning" class="text-xs font-normal"> Action required </span> </div> <p class="text-muted-foreground pt-1 text-xs">Below safety stock threshold · 4 POs in transit</p> </div> </div> <!-- Metric 4: Inbound Shipments Today --> <div ui-card class="shadow-xs"> <div ui-card-header class="flex flex-row items-center justify-between space-y-0 pb-2"> <h3 ui-card-title class="text-muted-foreground text-xs font-medium tracking-wider uppercase"> Inbound Shipments Today </h3> <div class="bg-success/10 text-success flex size-8 shrink-0 items-center justify-center rounded-md"> <lucide-icon [img]="Truck" class="size-4 shrink-0" /> </div> </div> <div ui-card-content class="space-y-1"> <div class="text-foreground text-2xl font-bold tracking-tight tabular-nums"> 4 <span class="text-muted-foreground text-sm font-normal">POs arriving</span> </div> <p class="text-muted-foreground pt-1 text-xs">2,450 units expected by 16:00 · Bay 02 & 05</p> </div> </div> </div> <!-- Inventory Filter & Search Toolbar --> <div ui-card class="shadow-xs"> <div ui-card-header class="border-border border-b pb-3"> <div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between"> <div class="flex flex-1 flex-wrap items-center gap-2"> <div class="relative w-full min-w-0 md:max-w-sm"> <lucide-icon [img]="Search" class="text-muted-foreground absolute top-1/2 left-3 size-4 shrink-0 -translate-y-1/2" /> <ui-input [value]="searchQuery()" (valueChange)="searchQuery.set($event)" placeholder="Search SKU or product" class="h-9 pl-9 text-xs sm:text-sm" /> </div> <!-- Category Filter --> <ui-select [value]="categoryFilter()" (valueChange)="categoryFilter.set($event)"> <button ui-select-trigger class="h-9 w-[160px] text-xs"> <ui-select-value placeholder="All Categories" /> </button> <ui-select-content> <ui-select-item value="all">All Categories</ui-select-item> <ui-select-item value="Footwear & Apparel">Footwear & Apparel</ui-select-item> <ui-select-item value="Outerwear">Outerwear</ui-select-item> <ui-select-item value="Packaging & Materials">Packaging & Materials</ui-select-item> <ui-select-item value="Cold Chain Supplies">Cold Chain Supplies</ui-select-item> <ui-select-item value="WMS Equipment">WMS Equipment</ui-select-item> </ui-select-content> </ui-select> <!-- Status Filter --> <ui-select [value]="statusFilter()" (valueChange)="statusFilter.set($event)"> <button ui-select-trigger class="h-9 w-[140px] text-xs"> <ui-select-value placeholder="All Stock Status" /> </button> <ui-select-content> <ui-select-item value="all">All Statuses</ui-select-item> <ui-select-item value="in_stock">In Stock</ui-select-item> <ui-select-item value="low_stock">Low Stock (Reorder)</ui-select-item> </ui-select-content> </ui-select> </div> <div class="text-muted-foreground flex items-center gap-2 text-xs"> <span >Showing <strong class="text-foreground tabular-nums">{{ filteredItems().length }}</strong> of {{ itemCount }} inventory items</span > </div> </div> </div> <div ui-card-content class="p-0"> <!-- Inventory Table --> <div class="overflow-x-auto"> <table ui-table> <thead ui-table-header> <tr ui-table-row class="hover:bg-transparent"> <th ui-table-head class="w-[180px] text-xs font-semibold">SKU & Barcode</th> <th ui-table-head class="min-w-[220px] text-xs font-semibold">Item & Category</th> <th ui-table-head class="min-w-[210px] text-xs font-semibold">Bin / Rack Location</th> <th ui-table-head class="text-right text-xs font-semibold">On Hand</th> <th ui-table-head class="text-right text-xs font-semibold">Allocated</th> <th ui-table-head class="text-right text-xs font-semibold">Available (ATP)</th> <th ui-table-head class="min-w-[160px] text-xs font-semibold">Reorder & Status</th> <th ui-table-head class="w-[60px] text-right text-xs font-semibold">Actions</th> </tr> </thead> <tbody ui-table-body> @for (item of filteredItems(); track item.id) { <tr ui-table-row class="hover:bg-muted/40 transition-colors"> <!-- SKU & Barcode --> <td ui-table-cell class="py-3"> <div class="space-y-0.5"> <div class="text-foreground font-mono text-xs font-semibold"> {{ item.sku }} </div> <div class="text-muted-foreground flex items-center gap-1 font-mono text-xs"> <lucide-icon [img]="Barcode" class="text-muted-foreground/70 size-3 shrink-0" /> <span>{{ item.barcode }}</span> </div> </div> </td> <!-- Item Name & Category --> <td ui-table-cell class="py-3"> <div class="space-y-0.5"> <div class="text-foreground text-sm font-medium"> {{ item.name }} </div> <div class="text-muted-foreground flex items-center gap-1.5 text-xs"> <span>{{ item.category }}</span> <span>·</span> <span class="font-mono text-xs">{{ item.palletId }}</span> </div> </div> </td> <!-- Bin / Rack Location Badge --> <td ui-table-cell class="py-3"> <div class="space-y-1"> <span ui-badge variant="outline" class="bg-muted/30 gap-1.5 font-mono text-xs font-medium"> <lucide-icon [img]="Tag" class="text-primary size-3 shrink-0" /> {{ item.binLocation }} </span> <div class="text-muted-foreground text-xs"> {{ item.zone }} </div> </div> </td> <!-- On Hand --> <td ui-table-cell class="py-3 text-right"> <div class="text-foreground text-sm font-semibold tabular-nums"> {{ item.onHand.toLocaleString() }} <span class="text-muted-foreground text-xs font-normal">units</span> </div> <div class="text-muted-foreground text-xs tabular-nums"> {{ item.unitVolume }} </div> </td> <!-- Allocated --> <td ui-table-cell class="py-3 text-right"> <div class="text-muted-foreground text-sm font-medium tabular-nums"> {{ item.allocated.toLocaleString() }} <span class="text-muted-foreground text-xs font-normal">units</span> </div> <div class="text-muted-foreground text-xs tabular-nums">{{ reservedPct(item) }}% reserved</div> </td> <!-- Available (ATP) --> <td ui-table-cell class="py-3 text-right"> <div class="text-foreground text-sm font-bold tabular-nums"> {{ item.available.toLocaleString() }} <span class="text-muted-foreground text-xs font-normal">units</span> </div> <div class="text-success text-xs font-medium">Ready to pick</div> </td> <!-- Reorder Threshold & Status --> <td ui-table-cell class="py-3"> <div class="space-y-1"> @if (item.status === 'in_stock') { <span ui-badge variant="success" class="gap-1 text-xs whitespace-normal"> <lucide-icon [img]="CheckCircle2" class="size-3 shrink-0" /> In Stock </span> } @else { <span ui-badge variant="warning" class="gap-1 text-xs"> <lucide-icon [img]="AlertTriangle" class="size-3 shrink-0" /> Low Stock - Reorder </span> } <div class="text-muted-foreground text-xs tabular-nums"> Min safety: {{ item.reorderThreshold }} units </div> </div> </td> <!-- Actions Menu --> <td ui-table-cell class="py-3 text-right"> <ui-dropdown-menu> <button ui-button ui-dropdown-menu-trigger variant="ghost" size="icon-sm" class="size-8 shrink-0" > <lucide-icon [img]="MoreHorizontal" class="size-4 shrink-0" /> <span class="sr-only">Open menu</span> </button> <ui-dropdown-menu-content align="end" class="w-48"> <ui-dropdown-menu-label class="text-muted-foreground text-xs" >Bin Actions</ui-dropdown-menu-label > <ui-dropdown-menu-item class="cursor-pointer gap-2" (select)="openTransferDrawer(item)"> <lucide-icon [img]="ArrowRightLeft" class="text-muted-foreground size-4 shrink-0" /> Transfer / Relocate Bin </ui-dropdown-menu-item> <ui-dropdown-menu-item class="cursor-pointer gap-2" (select)="handlePrintBarcode(item)"> <lucide-icon [img]="Printer" class="text-muted-foreground size-4 shrink-0" /> Print Barcode Label </ui-dropdown-menu-item> <ui-dropdown-menu-item class="cursor-pointer gap-2" (select)="handleCycleCount(item)"> <lucide-icon [img]="ClipboardCheck" class="text-muted-foreground size-4 shrink-0" /> Log Cycle Count </ui-dropdown-menu-item> <ui-dropdown-menu-separator /> <ui-dropdown-menu-item class="text-warning cursor-pointer gap-2" (select)="openReceiveDrawer()" > <lucide-icon [img]="PackagePlus" class="size-4 shrink-0" /> Receive Restock Batch </ui-dropdown-menu-item> </ui-dropdown-menu-content> </ui-dropdown-menu> </td> </tr> } @if (filteredItems().length === 0) { <tr ui-table-row> <td ui-table-cell [attr.colspan]="8" class="text-muted-foreground h-32 text-center text-sm"> No warehouse inventory items found matching your criteria. </td> </tr> } </tbody> </table> </div> </div> </div> <!-- Quick Transfer / Relocate Bin Drawer (Sheet) --> <div ui-sheet [open]="drawerOpen()" (openChange)="drawerOpen.set($event)"> <div ui-sheet-content side="right" class="w-full space-y-6 overflow-y-auto p-6 sm:max-w-md"> <!-- Drawer Header --> <div ui-sheet-header class="space-y-1 text-left"> <div class="flex items-center gap-2"> <div class="bg-primary/10 text-primary flex size-8 shrink-0 items-center justify-center rounded-md"> @if (drawerMode() === 'transfer') { <lucide-icon [img]="ArrowRightLeft" class="size-4 shrink-0" /> } @else { <lucide-icon [img]="PackagePlus" class="size-4 shrink-0" /> } </div> <h2 ui-sheet-title class="text-lg font-bold"> {{ drawerMode() === 'transfer' ? 'Relocate Bin / Pallet Transfer' : 'Receive Inventory Inbound' }} </h2> </div> <p ui-sheet-description class="text-muted-foreground text-xs"> {{ drawerMode() === 'transfer' ? 'Relocate inventory pallets between rack coordinates and generate WMS transfer manifests.' : 'Process inbound freight shipments, inspect UPC barcodes, and assign initial staging bins.' }} </p> </div> <!-- Mode: Transfer Bin --> @if (drawerMode() === 'transfer') { @if (selectedItem(); as item) { <!-- Item Card Summary --> <div class="border-border bg-muted/20 space-y-3 rounded-lg border p-4"> <div class="flex items-start justify-between gap-2"> <div> <div class="text-primary font-mono text-xs font-semibold">{{ item.sku }}</div> <div class="text-foreground text-sm font-medium">{{ item.name }}</div> <div class="text-muted-foreground text-xs">{{ item.category }}</div> </div> <span ui-badge variant="outline" class="font-mono text-xs">{{ item.palletId }}</span> </div> <ui-separator /> <div class="grid grid-cols-1 gap-2 text-xs sm:grid-cols-2"> <div> <span class="text-muted-foreground">Current Bin:</span> <p class="text-foreground mt-0.5 font-mono text-xs font-medium">{{ item.binLocation }}</p> </div> <div> <span class="text-muted-foreground">Available to Move:</span> <p class="text-success mt-0.5 font-bold tabular-nums">{{ item.available }} units</p> </div> </div> </div> <!-- Transfer Form Fields --> <div class="space-y-4"> <div class="space-y-1.5"> <label class="text-foreground text-xs font-medium">Destination Rack / Bin Location</label> <ui-select [value]="transferDestination()" (valueChange)="transferDestination.set($event)"> <button ui-select-trigger class="w-full text-xs"> <ui-select-value placeholder="Select target bin location" /> </button> <ui-select-content> <ui-select-item value="bin-dest-1" >Aisle 01 · Rack A · Shelf 03 (Zone A-North · Standard Rack)</ui-select-item > <ui-select-item value="bin-dest-2" >Aisle 03 · Rack B · Shelf 01 (Zone A-West · Fast-Pick)</ui-select-item > <ui-select-item value="bin-dest-3" >Aisle 08 · Rack C · Shelf 04 (Zone B-Bulk · High-Bay)</ui-select-item > <ui-select-item value="bin-dest-4" >Aisle 05 · Rack D · Shelf 02 (Zone C-Cold · Cold Bay)</ui-select-item > </ui-select-content> </ui-select> </div> <div class="space-y-1.5"> <div class="flex items-center justify-between"> <label class="text-foreground text-xs font-medium">Transfer Quantity (Units)</label> <span class="text-muted-foreground text-xs tabular-nums">Max: {{ item.available }} units</span> </div> <ui-input [value]="transferQty()" (valueChange)="setTransferQty($event)" type="number" [min]="1" [max]="item.available" class="text-xs tabular-nums sm:text-sm" /> <div class="flex gap-1.5 pt-1"> <button type="button" ui-button variant="outline" size="xs" class="flex-1 text-xs" (click)="setQuickQty(25)" > 25 </button> <button type="button" ui-button variant="outline" size="xs" class="flex-1 text-xs" (click)="setQuickQty(50)" > 50 </button> <button type="button" ui-button variant="outline" size="xs" class="flex-1 text-xs" (click)="setQuickQty(100)" > 100 </button> <button type="button" ui-button variant="outline" size="xs" class="flex-1 text-xs" (click)="setQuickQty(item.available)" > All ({{ item.available }}) </button> </div> </div> <div class="space-y-1.5"> <label class="text-foreground text-xs font-medium">Pallet ID / Batch Lot</label> <ui-input [value]="transferPalletId()" (valueChange)="transferPalletId.set($event)" class="font-mono text-xs" /> </div> <div class="space-y-1.5"> <label class="text-foreground text-xs font-medium">Transfer Reason</label> <ui-select [value]="transferReason()" (valueChange)="transferReason.set($event)"> <button ui-select-trigger class="w-full text-xs"> <ui-select-value placeholder="Select transfer reason" /> </button> <ui-select-content> <ui-select-item value="reslotting">Zone Re-slotting (Velocity Optimization)</ui-select-item> <ui-select-item value="consolidation">Consolidation & Space Reclamation</ui-select-item> <ui-select-item value="staging">Fast-Pick Outbound Wave Staging</ui-select-item> <ui-select-item value="rack-repair">Damaged Rack Maintenance Relocation</ui-select-item> <ui-select-item value="quarantine">Quarantine & Quality Audit Transfer</ui-select-item> </ui-select-content> </ui-select> </div> <!-- Verified Scanner / Operator Badge --> <div class="border-border bg-muted/40 space-y-1 rounded-md border p-3 text-xs"> <div class="text-muted-foreground flex items-center justify-between"> <span class="flex items-center gap-1.5"> <lucide-icon [img]="QrCode" class="text-primary size-3.5 shrink-0" /> WMS Scanner Terminal </span> <span class="text-foreground font-mono font-medium">TC57-OP44</span> </div> <p class="text-muted-foreground">Operator: Marcus Vance (Badge #OP-921)</p> </div> </div> } } @else { <!-- Mode: Receive Inbound PO --> <div class="space-y-4"> <div class="space-y-1.5"> <label class="text-foreground text-xs font-medium">Inbound Purchase Order</label> <ui-select [value]="receivePo()" (valueChange)="receivePo.set($event)"> <button ui-select-trigger class="w-full text-xs"> <ui-select-value placeholder="Select purchase order" /> </button> <ui-select-content> <ui-select-item value="po-8910">PO-2026-8910 · Acme Footwear Corp (500 units)</ui-select-item> <ui-select-item value="po-8914">PO-2026-8914 · Pacific Packaging Ltd (1,200 units)</ui-select-item> <ui-select-item value="po-8919">PO-2026-8919 · Thermal Supply Chain Co (750 units)</ui-select-item> </ui-select-content> </ui-select> </div> <div class="space-y-1.5"> <label class="text-foreground text-xs font-medium">Target SKU / Barcode</label> <ui-input [value]="receiveSku()" (valueChange)="receiveSku.set($event)" placeholder="#SKU-84920 or UPC code" class="font-mono text-xs" /> </div> <div class="space-y-1.5"> <label class="text-foreground text-xs font-medium">Received Quantity (Units)</label> <ui-input [value]="receiveQty()" (valueChange)="setReceiveQty($event)" type="number" class="text-xs tabular-nums" /> </div> <div class="space-y-1.5"> <label class="text-foreground text-xs font-medium">Assigned Inbound Staging Bay</label> <ui-select [value]="receiveBin()" (valueChange)="receiveBin.set($event)"> <button ui-select-trigger class="w-full text-xs"> <ui-select-value placeholder="Select staging bin" /> </button> <ui-select-content> <ui-select-item value="bay-02">Staging Bay Inbound-02 (Aisle 00 · Cross-Dock)</ui-select-item> <ui-select-item value="bay-05">Staging Bay Inbound-05 (Cold Holding)</ui-select-item> <ui-select-item value="bay-08">Bulk Floor Staging Area B</ui-select-item> </ui-select-content> </ui-select> </div> <div class="text-foreground border-success/20 bg-success/5 space-y-1 rounded-md border p-3 text-xs"> <div class="text-success flex items-center gap-1.5 font-medium"> <lucide-icon [img]="Check" class="size-3.5 shrink-0" /> Inbound Freight Scan Ready </div> <p class="text-muted-foreground"> Pallet tags will be generated and printed automatically upon receipt confirmation. </p> </div> </div> } <!-- Drawer Footer --> <div ui-sheet-footer class="border-border flex flex-col-reverse gap-2 border-t pt-4 sm:flex-row sm:justify-end" > <div ui-sheet-close> <button ui-button variant="outline" class="w-full sm:w-auto">Cancel</button> </div> @if (drawerMode() === 'transfer') { <button ui-button class="w-full gap-1.5 sm:w-auto" (click)="handleConfirmTransfer()"> <lucide-icon [img]="ArrowRightLeft" class="size-4 shrink-0" /> Confirm Bin Transfer </button> } @else { <button ui-button class="w-full gap-1.5 sm:w-auto" (click)="handleConfirmReceive()"> <lucide-icon [img]="ArrowDownToLine" class="size-4 shrink-0" /> Confirm Receipt </button> } </div> </div> </div> </div> `, }) export class UiWarehouseInventoryGridComponent { protected readonly AlertTriangle = AlertTriangle protected readonly ArrowDownToLine = ArrowDownToLine protected readonly ArrowRightLeft = ArrowRightLeft protected readonly ArrowUpRight = ArrowUpRight protected readonly Barcode = Barcode protected readonly Boxes = Boxes protected readonly Building2 = Building2 protected readonly Check = Check protected readonly CheckCircle2 = CheckCircle2 protected readonly ClipboardCheck = ClipboardCheck protected readonly MoreHorizontal = MoreHorizontal protected readonly Package = Package protected readonly PackagePlus = PackagePlus protected readonly Printer = Printer protected readonly QrCode = QrCode protected readonly Search = Search protected readonly Tag = Tag protected readonly Truck = Truck protected readonly Warehouse = Warehouse protected readonly X = X @Input('class') className?: string readonly facility = signal('bay-area') readonly searchQuery = signal('') readonly categoryFilter = signal('all') readonly statusFilter = signal('all') readonly zoneFilter = signal('all') readonly drawerOpen = signal(false) readonly drawerMode = signal<'transfer' | 'receive'>('transfer') readonly selectedItem = signal<WarehouseItem | null>(null) readonly transferDestination = signal('bin-dest-1') readonly transferQty = signal(50) readonly transferPalletId = signal('PLT-84920-04') readonly transferReason = signal('reslotting') readonly receivePo = signal('po-8910') readonly receiveSku = signal('SKU-84920') readonly receiveQty = signal(250) readonly receiveBin = signal('bay-02') readonly actionFeedback = signal<string | null>(null) private feedbackTimer: ReturnType<typeof setTimeout> | undefined readonly itemCount = warehouseItems.length readonly filteredItems = computed(() => { const q = this.searchQuery().trim().toLowerCase() const category = this.categoryFilter() const status = this.statusFilter() const zone = this.zoneFilter() return warehouseItems.filter((item) => { const matchesSearch = !q || item.name.toLowerCase().includes(q) || item.sku.toLowerCase().includes(q) || item.barcode.toLowerCase().includes(q) || item.binLocation.toLowerCase().includes(q) || item.category.toLowerCase().includes(q) const matchesCategory = category === 'all' || item.category === category const matchesStatus = status === 'all' || item.status === status const matchesZone = zone === 'all' || item.zone.toLowerCase().includes(zone.toLowerCase()) return matchesSearch && matchesCategory && matchesStatus && matchesZone }) }) get rootClass(): string { return cn('w-full space-y-6', this.className) } reservedPct(item: WarehouseItem): number { return Math.round((item.allocated / item.onHand) * 100) } setTransferQty(v: string): void { const n = Number(v) this.transferQty.set(Number.isFinite(n) ? n : 0) } setReceiveQty(v: string): void { const n = Number(v) this.receiveQty.set(Number.isFinite(n) ? n : 0) } setQuickQty(qty: number): void { const item = this.selectedItem() this.transferQty.set(item ? Math.min(qty, item.available) : qty) } openTransferDrawer(item?: WarehouseItem): void { const target = item ?? warehouseItems[0]! this.selectedItem.set(target) this.transferPalletId.set(target.palletId) this.transferQty.set(Math.min(50, target.available)) this.drawerMode.set('transfer') this.drawerOpen.set(true) } openReceiveDrawer(): void { this.drawerMode.set('receive') this.drawerOpen.set(true) } private flashFeedback(message: string, ms: number): void { this.actionFeedback.set(message) clearTimeout(this.feedbackTimer) this.feedbackTimer = setTimeout(() => this.actionFeedback.set(null), ms) } handleConfirmTransfer(): void { const item = this.selectedItem() if (!item) return this.flashFeedback(`Successfully relocated ${this.transferQty()} units of ${item.sku} to destination bin.`, 4000) this.drawerOpen.set(false) } handleConfirmReceive(): void { this.flashFeedback( `Received ${this.receiveQty()} units for ${this.receivePo().toUpperCase()} staged into Bay 02.`, 4000, ) this.drawerOpen.set(false) } handlePrintBarcode(item: WarehouseItem): void { this.flashFeedback(`Barcode label queued for ${item.sku} (${item.barcode}) on Zebra Printer #2.`, 3500) } handleCycleCount(item: WarehouseItem): void { this.flashFeedback(`Cycle count logged and verified for bin ${item.binLocation}.`, 3500) } }src/app/components/blocks/warehouse-inventory-grid/index.ts0.1 kB
export { UiWarehouseInventoryGridComponent, type StockStatus, type WarehouseItem, } from './warehouse-inventory-grid.component'
Raw manifest:https://uipkge.dev/r/angular/warehouse-inventory-grid.json