Menu

Change language

GitHub repo

Order Management Page

blockcommerce

E-commerce and SaaS order fulfillment admin table: header with export CSV and new order action, 4 KPI summary metric cards, search and multi-status filter toolbar with date range selector, selectable order rows with customer avatars and status badges, slide-over order details drawer with line items breakdown and tracking info, bulk action bar, and pagination.

Installation

$npx shadcn@latest add https://uipkge.dev/r/lit/order-management-page.json
Manifest:https://uipkge.dev/r/lit/order-management-page.jsonInstalls to:src/uip/blocks/order-management-page/

Variants

Loading interactive previews…

Schema

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

OrderItem
interface OrderItem {
  name: string
  sku: string
  quantity: number
  price: number
}
CustomerInfo
interface CustomerInfo {
  name: string
  email: string
  avatar?: string
  address: string
  city: string
  state: string
  zip: string
}
Order
interface Order {
  id: string
  orderNumber: string
  date: string
  customer: CustomerInfo
  items: OrderItem[]
  status: OrderStatus
  paymentMethod: string
  subtotal: number
  shipping: number
  tax: number
  total: number
  carrier?: string
  trackingNumber?: string
}
HTMLElementTagNameMap
interface HTMLElementTagNameMap {
    'uip-order-management-page': UipOrderManagementPage
  }
}

Files installed (1)

  • src/uip/blocks/order-management-page/order-management-page.ts37.6 kB
    import { LitElement, css, html, nothing } from 'lit'
    import {
      ArrowUpRight,
      Calendar,
      ChevronDown,
      Clock,
      DollarSign,
      Download,
      Eye,
      FileText,
      MoreHorizontal,
      PackageCheck,
      Plus,
      RotateCcw,
      Search,
      ShoppingCart,
      TrendingUp,
      Truck,
    } from 'lucide'
    import { cn } from '../../lib/utils'
    import { tailwind } from '../../lib/styles'
    import { ThemeController } from '../../lib/theme'
    import { icon } from '../../lib/icon'
    // Side-effect imports: register the composed elements for standalone use
    // (consumers who install only this block still get working primitives).
    import '../../components/avatar/avatar'
    import '../../components/badge/badge'
    import '../../components/button/button'
    import '../../components/card/card'
    import '../../components/checkbox/checkbox'
    import '../../components/dropdown-menu/dropdown-menu'
    import '../../components/input/input'
    import type { UipInput } from '../../components/input/input'
    import '../../components/sheet/sheet'
    
    export type OrderStatus = 'paid' | 'processing' | 'shipped' | 'refunded'
    
    export interface OrderItem {
      name: string
      sku: string
      quantity: number
      price: number
    }
    
    export interface CustomerInfo {
      name: string
      email: string
      avatar?: string
      address: string
      city: string
      state: string
      zip: string
    }
    
    export interface Order {
      id: string
      orderNumber: string
      date: string
      customer: CustomerInfo
      items: OrderItem[]
      status: OrderStatus
      paymentMethod: string
      subtotal: number
      shipping: number
      tax: number
      total: number
      carrier?: string
      trackingNumber?: string
    }
    
    const orders: Order[] = [
      {
        id: 'ord-1',
        orderNumber: '#ORD-8492',
        date: 'Oct 24, 2026',
        customer: {
          name: 'Sarah Chen',
          email: 'sarah.chen@example.com',
          address: '742 Evergreen Terrace',
          city: 'Springfield',
          state: 'OR',
          zip: '97477',
        },
        items: [
          { name: 'Ergonomic Mechanical Keyboard', sku: 'KB-ERGO-01', quantity: 1, price: 189 },
          { name: 'Desk Mat Pro (Midnight)', sku: 'MAT-PRO-BLK', quantity: 1, price: 39 },
        ],
        status: 'processing',
        paymentMethod: 'Mastercard •••• 8492',
        subtotal: 228,
        shipping: 0,
        tax: 18.24,
        total: 246.24,
        carrier: 'FedEx Priority',
        trackingNumber: 'FX-9281034-US',
      },
      {
        id: 'ord-2',
        orderNumber: '#ORD-8491',
        date: 'Oct 24, 2026',
        customer: {
          name: 'Marcus Vance',
          email: 'marcus.v@acme-corp.com',
          address: '100 Enterprise Way, Suite 400',
          city: 'San Francisco',
          state: 'CA',
          zip: '94105',
        },
        items: [
          { name: 'UltraWide Monitor 34"', sku: 'MON-34-UW', quantity: 1, price: 699 },
          { name: 'Dual Monitor Arm Mount', sku: 'ACC-ARM-02', quantity: 1, price: 129 },
          { name: 'Braided DisplayPort Cable 2m', sku: 'CAB-DP-02', quantity: 1, price: 24 },
        ],
        status: 'shipped',
        paymentMethod: 'Visa •••• 4242',
        subtotal: 852,
        shipping: 0,
        tax: 68.16,
        total: 920.16,
        carrier: 'UPS Ground',
        trackingNumber: '1Z999AA10123456784',
      },
      {
        id: 'ord-3',
        orderNumber: '#ORD-8490',
        date: 'Oct 23, 2026',
        customer: {
          name: 'Elena Rostova',
          email: 'elena.rostova@techflow.io',
          address: '450 Innovation Parkway',
          city: 'Austin',
          state: 'TX',
          zip: '78701',
        },
        items: [{ name: 'SaaS Enterprise Annual License', sku: 'LIC-ENT-YR', quantity: 1, price: 1200 }],
        status: 'paid',
        paymentMethod: 'Visa •••• 9104',
        subtotal: 1200,
        shipping: 0,
        tax: 0,
        total: 1200,
      },
      {
        id: 'ord-4',
        orderNumber: '#ORD-8489',
        date: 'Oct 23, 2026',
        customer: {
          name: 'David Kim',
          email: 'david.kim@designly.co',
          address: '88 King Street West',
          city: 'Seattle',
          state: 'WA',
          zip: '98104',
        },
        items: [
          { name: 'USB-C Multiport Dock Pro', sku: 'HUB-USBC-8P', quantity: 2, price: 79 },
          { name: 'Thunderbolt 4 Braided Cable', sku: 'CAB-TB4-1M', quantity: 2, price: 29 },
        ],
        status: 'shipped',
        paymentMethod: 'Apple Pay',
        subtotal: 216,
        shipping: 0,
        tax: 17.28,
        total: 233.28,
        carrier: 'DHL Express',
        trackingNumber: 'DHL-8492019-EXP',
      },
      {
        id: 'ord-5',
        orderNumber: '#ORD-8488',
        date: 'Oct 22, 2026',
        customer: {
          name: 'Olivia Taylor',
          email: 'olivia.t@summit.ai',
          address: '12 Marina Boulevard',
          city: 'Boston',
          state: 'MA',
          zip: '02210',
        },
        items: [{ name: 'Noise-Cancelling Studio Headphones', sku: 'AUD-NC-700', quantity: 1, price: 349 }],
        status: 'refunded',
        paymentMethod: 'Visa •••• 1128',
        subtotal: 349,
        shipping: 0,
        tax: 27.92,
        total: 376.92,
      },
      {
        id: 'ord-6',
        orderNumber: '#ORD-8487',
        date: 'Oct 22, 2026',
        customer: {
          name: 'Liam Johansson',
          email: 'liam.j@nordic.se',
          address: '240 Queen Street',
          city: 'New York',
          state: 'NY',
          zip: '10001',
        },
        items: [
          { name: 'Aluminum Laptop Riser', sku: 'STN-ALUM-SLV', quantity: 1, price: 65 },
          { name: 'Wireless Precision Mouse', sku: 'MOU-WL-01', quantity: 1, price: 99 },
        ],
        status: 'processing',
        paymentMethod: 'Mastercard •••• 5531',
        subtotal: 164,
        shipping: 0,
        tax: 13.12,
        total: 177.12,
        carrier: 'USPS Priority',
        trackingNumber: '9400100000000000000000',
      },
      {
        id: 'ord-7',
        orderNumber: '#ORD-8486',
        date: 'Oct 21, 2026',
        customer: {
          name: 'Aisha Patel',
          email: 'aisha.patel@globalnet.com',
          address: '500 Central Avenue',
          city: 'Chicago',
          state: 'IL',
          zip: '60601',
        },
        items: [{ name: 'Cloud Storage Tier 3 (500GB)', sku: 'SRV-STR-500', quantity: 1, price: 450 }],
        status: 'paid',
        paymentMethod: 'Amex •••• 3009',
        subtotal: 450,
        shipping: 0,
        tax: 36,
        total: 486,
      },
      {
        id: 'ord-8',
        orderNumber: '#ORD-8485',
        date: 'Oct 20, 2026',
        customer: {
          name: 'Lucas Moreau',
          email: 'lucas.m@atelier.fr',
          address: '15 Rue de la Paix',
          city: 'Boulder',
          state: 'CO',
          zip: '80302',
        },
        items: [
          { name: 'Broadcast Studio Microphone', sku: 'MIC-POD-01', quantity: 1, price: 229 },
          { name: 'Dual-Layer Pop Filter', sku: 'ACC-POP-01', quantity: 1, price: 25 },
          { name: 'Gold-Plated XLR Cable 3m', sku: 'CAB-XLR-03', quantity: 1, price: 19 },
        ],
        status: 'shipped',
        paymentMethod: 'Visa •••• 7712',
        subtotal: 273,
        shipping: 0,
        tax: 21.84,
        total: 294.84,
        carrier: 'FedEx Ground',
        trackingNumber: 'FX-7712093-US',
      },
    ]
    
    const statusBadgeConfig: Record<OrderStatus, { label: string; class: string; dot: string }> = {
      paid: { label: 'Paid', class: 'bg-success/10 text-success border-success/20', dot: 'bg-success' },
      processing: { label: 'Processing', class: 'bg-info/10 text-info border-info/20', dot: 'bg-info' },
      shipped: { label: 'Shipped', class: 'bg-chart-1/10 text-chart-1 border-chart-1/20', dot: 'bg-chart-1' },
      refunded: { label: 'Refunded', class: 'bg-destructive/10 text-destructive border-destructive/20', dot: 'bg-destructive' },
    }
    
    function formatCurrency(val: number) {
      return val.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
    }
    
    function initials(name: string) {
      return name
        .split(' ')
        .map((p) => p[0])
        .slice(0, 2)
        .join('')
        .toUpperCase()
    }
    
    /**
     * <uip-order-management-page> — the registry OrderManagementPage block as a web component.
     *
     * Class strings and order data are React's verbatim. React state becomes
     * { state: true } fields. The block takes no props and fires no events; search,
     * status filter, bulk selection, and the details sheet are internal.
     *
     * Two deliberate departures: (1) the orders grid uses a native <table> with
     * the registry Table part classes copied verbatim — <uip-table>'s data-driven
     * API can't express per-row/per-cell custom classes, badges, or row clicks;
     * (2) the search icon moves from an absolute overlay into <uip-input>'s
     * prefix slot (same visual result, no overlay positioning to maintain).
     */
    export class UipOrderManagementPage extends LitElement {
      static shadowRootOptions = { ...LitElement.shadowRootOptions, delegatesFocus: true }
      // :host display can't be a utility class (the host has no template of its own).
      static styles = [tailwind, css`:host { display: block; }`]
    
      static properties = {
        search: { state: true },
        statusFilter: { state: true },
        selected: { state: true },
        selectedOrder: { state: true },
        drawerOpen: { state: true },
      }
    
      private search = ''
      private statusFilter: 'all' | OrderStatus = 'all'
      private selected: Set<string> = new Set()
      private selectedOrder: Order | null = null
      private drawerOpen = false
    
      constructor() {
        super()
        new ThemeController(this)
      }
    
      connectedCallback() {
        super.connectedCallback()
        this.setAttribute('data-uipkge', '')
        this.setAttribute('data-slot', 'order-management-page')
      }
    
      private get filteredOrders(): Order[] {
        const query = this.search.trim().toLowerCase()
        return orders.filter((order) => {
          const matchesStatus = this.statusFilter === 'all' || order.status === this.statusFilter
          const matchesQuery =
            !query ||
            order.orderNumber.toLowerCase().includes(query) ||
            order.customer.name.toLowerCase().includes(query) ||
            order.customer.email.toLowerCase().includes(query)
          return matchesStatus && matchesQuery
        })
      }
    
      private get statusCounts(): Record<string, number> {
        const counts: Record<string, number> = { all: orders.length, paid: 0, processing: 0, shipped: 0, refunded: 0 }
        for (const ord of orders) counts[ord.status] = (counts[ord.status] || 0) + 1
        return counts
      }
    
      private toggleAll(checked: boolean) {
        this.selected = checked ? new Set(this.filteredOrders.map((ord) => ord.id)) : new Set()
      }
    
      private toggleRow(id: string, checked: boolean) {
        const next = new Set(this.selected)
        if (checked) next.add(id)
        else next.delete(id)
        this.selected = next
      }
    
      private clearSelection() {
        this.selected = new Set()
      }
    
      private openOrderDetails(order: Order) {
        this.selectedOrder = order
        this.drawerOpen = true
      }
    
      render() {
        const filteredOrders = this.filteredOrders
        const counts = this.statusCounts
        const statusOptions = [
          { value: 'all' as const, label: 'All', count: counts.all },
          { value: 'paid' as const, label: 'Paid', count: counts.paid },
          { value: 'processing' as const, label: 'Processing', count: counts.processing },
          { value: 'shipped' as const, label: 'Shipped', count: counts.shipped },
          { value: 'refunded' as const, label: 'Refunded', count: counts.refunded },
        ]
        const allSelected = filteredOrders.length > 0 && filteredOrders.every((ord) => this.selected.has(ord.id))
        const someSelected = filteredOrders.some((ord) => this.selected.has(ord.id))
        const order = this.selectedOrder
    
        return html`
          <div data-slot="order-management-page" class="w-full space-y-6">
            <div class="flex flex-wrap items-center justify-between gap-4">
              <div>
                <h2 class="text-foreground text-2xl font-bold tracking-tight">Orders</h2>
                <p class="text-muted-foreground mt-1 text-sm">Manage and track customer purchases</p>
              </div>
              <div class="flex items-center gap-2">
                <uip-button aria-label="Download attachment" variant="outline" size="sm" class="text-xs">
                  ${icon(Download, 'download', 'mr-1.5 size-3.5')} Export CSV
                </uip-button>
                <uip-button size="sm" class="text-xs"> ${icon(Plus, 'plus', 'mr-1.5 size-3.5')} New Order </uip-button>
              </div>
            </div>
    
            <div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
              <uip-card class="shadow-xs">
                <uip-card-content class="p-4 sm:p-5">
                  <div class="flex items-center justify-between">
                    <p class="text-muted-foreground text-xs font-medium tracking-wider uppercase">Total Orders</p>
                    <div class="bg-muted text-muted-foreground flex size-8 items-center justify-center rounded-md">
                      ${icon(ShoppingCart, 'shopping-cart', 'size-4')}
                    </div>
                  </div>
                  <div class="mt-3 flex flex-wrap items-baseline justify-between gap-2">
                    <span class="text-foreground text-2xl font-bold tracking-tight tabular-nums">1,429</span>
                    <span class="text-success inline-flex items-center gap-0.5 text-xs font-medium">
                      ${icon(TrendingUp, 'trending-up', 'size-3.5')} +8.2%
                    </span>
                  </div>
                  <p class="text-muted-foreground mt-1 text-xs">vs. previous 30 days</p>
                </uip-card-content>
              </uip-card>
    
              <uip-card class="shadow-xs">
                <uip-card-content class="p-4 sm:p-5">
                  <div class="flex items-center justify-between">
                    <p class="text-muted-foreground text-xs font-medium tracking-wider uppercase">Pending Fulfillment</p>
                    <div class="bg-warning/10 text-warning flex size-8 items-center justify-center rounded-md">
                      ${icon(Clock, 'clock', 'size-4')}
                    </div>
                  </div>
                  <div class="mt-3 flex flex-wrap items-baseline justify-between gap-2">
                    <span class="text-foreground text-2xl font-bold tracking-tight tabular-nums">28</span>
                    <span class="bg-warning/10 text-warning inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium">
                      12 urgent
                    </span>
                  </div>
                  <p class="text-muted-foreground mt-1 text-xs">Requires warehouse dispatch</p>
                </uip-card-content>
              </uip-card>
    
              <uip-card class="shadow-xs">
                <uip-card-content class="p-4 sm:p-5">
                  <div class="flex items-center justify-between">
                    <p class="text-muted-foreground text-xs font-medium tracking-wider uppercase">Processing</p>
                    <div class="bg-info/10 text-info flex size-8 items-center justify-center rounded-md">
                      ${icon(PackageCheck, 'package-check', 'size-4')}
                    </div>
                  </div>
                  <div class="mt-3 flex flex-wrap items-baseline justify-between gap-2">
                    <span class="text-foreground text-2xl font-bold tracking-tight tabular-nums">45</span>
                    <span class="bg-info/10 text-info inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium">
                      In queue
                    </span>
                  </div>
                  <p class="text-muted-foreground mt-1 text-xs">Packaging & label generation</p>
                </uip-card-content>
              </uip-card>
    
              <uip-card class="shadow-xs">
                <uip-card-content class="p-4 sm:p-5">
                  <div class="flex items-center justify-between">
                    <p class="text-muted-foreground text-xs font-medium tracking-wider uppercase">Total Revenue</p>
                    <div class="bg-muted text-muted-foreground flex size-8 items-center justify-center rounded-md">
                      ${icon(DollarSign, 'dollar-sign', 'size-4')}
                    </div>
                  </div>
                  <div class="mt-3 flex flex-wrap items-baseline justify-between gap-2">
                    <span class="text-foreground text-2xl font-bold tracking-tight tabular-nums">$128,450</span>
                    <span class="text-success inline-flex items-center gap-0.5 text-xs font-medium">
                      ${icon(ArrowUpRight, 'arrow-up-right', 'size-3.5')} +14.5%
                    </span>
                  </div>
                  <p class="text-muted-foreground mt-1 text-xs">vs. previous period</p>
                </uip-card-content>
              </uip-card>
            </div>
    
            <div class="flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between">
              <div class="flex flex-1 flex-wrap items-center gap-2">
                <uip-input
                  .value=${this.search}
                  placeholder="Search order ID, customer..."
                  class="h-9 min-w-[240px] flex-1 text-xs sm:max-w-xs"
                  @input=${(e: Event) => {
                    this.search = (e.target as UipInput).value ?? ''
                  }}
                >
                  <span slot="prefix">${icon(Search, 'search', 'size-4')}</span>
                </uip-input>
    
                <div class="bg-muted/60 flex items-center gap-1 rounded-lg border p-1">
                  ${statusOptions.map(
                    (status) => html`<button
                      type="button"
                      class=${cn(
                        'rounded-md px-2.5 py-1 text-xs font-medium transition-colors',
                        this.statusFilter === status.value
                          ? 'bg-background text-foreground shadow-xs'
                          : 'text-muted-foreground hover:text-foreground',
                      )}
                      @click=${() => {
                        this.statusFilter = status.value
                      }}
                    >
                      ${status.label}
                      ${status.count !== undefined
                        ? html`<span
                            class=${cn(
                              'ml-1.5 rounded-full px-1.5 py-0.5 text-xs font-normal tabular-nums',
                              this.statusFilter === status.value ? 'bg-muted text-foreground' : 'text-muted-foreground',
                            )}
                          >
                            ${status.count}
                          </span>`
                        : nothing}
                    </button>`,
                  )}
                </div>
              </div>
    
              <div class="flex items-center gap-2">
                <uip-button variant="outline" size="sm" class="text-xs font-normal">
                  ${icon(Calendar, 'calendar', 'text-muted-foreground mr-1.5 size-3.5')} Last 30 days
                  ${icon(ChevronDown, 'chevron-down', 'text-muted-foreground ml-1.5 size-3.5')}
                </uip-button>
              </div>
            </div>
    
            ${this.selected.size > 0
              ? html`<div class="bg-muted/50 flex flex-wrap items-center justify-between gap-2 rounded-lg border px-4 py-2.5 shadow-xs">
                  <div class="flex items-center gap-2">
                    <span class="bg-primary text-primary-foreground flex size-5 items-center justify-center rounded-full text-xs font-medium">
                      ${this.selected.size}
                    </span>
                    <span class="text-foreground text-sm font-medium">
                      ${this.selected.size} ${this.selected.size === 1 ? 'order' : 'orders'} selected
                    </span>
                  </div>
                  <div class="flex items-center gap-2">
                    <uip-button
                      aria-label="Download attachment"
                      variant="outline"
                      size="sm"
                      class="text-xs"
                      @click=${() => this.clearSelection()}
                    >
                      ${icon(Download, 'download', 'mr-1.5 size-3.5')} Export selected
                    </uip-button>
                    <uip-button variant="outline" size="sm" class="text-xs" @click=${() => this.clearSelection()}>
                      ${icon(Truck, 'truck', 'mr-1.5 size-3.5')} Mark as shipped
                    </uip-button>
                    <uip-button
                      variant="ghost"
                      size="sm"
                      class="text-destructive hover:text-destructive text-xs"
                      @click=${() => this.clearSelection()}
                    >
                      Cancel
                    </uip-button>
                  </div>
                </div>`
              : nothing}
    
            <div data-uipkge="" data-slot="table-container" class="bg-card relative w-full overflow-auto rounded-lg border shadow-xs">
              <table data-uipkge="" data-slot="table" class="w-full caption-bottom text-sm">
                <thead data-uipkge="" data-slot="table-header" class="bg-muted/50 [&_tr]:border-b">
                  <tr data-uipkge="" data-slot="table-row" class="border-b transition-colors duration-150 hover:bg-transparent">
                    <th
                      data-uipkge=""
                      data-slot="table-head"
                      class="text-foreground h-10 px-3 text-left align-middle text-sm font-medium whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px] w-10"
                    >
                      <uip-checkbox
                        .checked=${allSelected}
                        ?indeterminate=${!allSelected && someSelected}
                        aria-label="Select all orders"
                        @checked-change=${(e: CustomEvent<{ checked: boolean }>) => this.toggleAll(e.detail.checked)}
                      ></uip-checkbox>
                    </th>
                    <th
                      data-uipkge=""
                      data-slot="table-head"
                      class="text-foreground h-10 px-3 text-left align-middle text-sm font-medium whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px] min-w-[110px]"
                    >
                      Order ID
                    </th>
                    <th
                      data-uipkge=""
                      data-slot="table-head"
                      class="text-foreground h-10 px-3 text-left align-middle text-sm font-medium whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px] min-w-[100px]"
                    >
                      Date
                    </th>
                    <th
                      data-uipkge=""
                      data-slot="table-head"
                      class="text-foreground h-10 px-3 text-left align-middle text-sm font-medium whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px] min-w-[200px]"
                    >
                      Customer
                    </th>
                    <th
                      data-uipkge=""
                      data-slot="table-head"
                      class="text-foreground h-10 px-3 text-left align-middle text-sm font-medium whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px] min-w-[220px]"
                    >
                      Items
                    </th>
                    <th
                      data-uipkge=""
                      data-slot="table-head"
                      class="text-foreground h-10 px-3 text-left align-middle text-sm font-medium whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px] min-w-[90px] text-right"
                    >
                      Total
                    </th>
                    <th
                      data-uipkge=""
                      data-slot="table-head"
                      class="text-foreground h-10 px-3 text-left align-middle text-sm font-medium whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px] min-w-[120px]"
                    >
                      Fulfillment
                    </th>
                    <th
                      data-uipkge=""
                      data-slot="table-head"
                      class="text-foreground h-10 px-3 text-left align-middle text-sm font-medium whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px] w-10 text-right"
                    >
                      <span class="sr-only">Actions</span>
                    </th>
                  </tr>
                </thead>
                <tbody data-uipkge="" data-slot="table-body" class="[&_tr:last-child]:border-0">
                  ${filteredOrders.map(
                    (ord) => html`<tr
                      data-uipkge=""
                      data-slot="table-row"
                      class="data-[state=selected]:bg-muted border-b transition-colors duration-150 group hover:bg-muted/40 cursor-pointer"
                      @click=${() => this.openOrderDetails(ord)}
                    >
                      <td
                        data-uipkge=""
                        data-slot="table-cell"
                        class="px-3 py-2 align-middle text-sm whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px] w-10"
                        @click=${(e: Event) => e.stopPropagation()}
                      >
                        <uip-checkbox
                          .checked=${this.selected.has(ord.id)}
                          aria-label=${`Select order ${ord.orderNumber}`}
                          @checked-change=${(e: CustomEvent<{ checked: boolean }>) => this.toggleRow(ord.id, e.detail.checked)}
                        ></uip-checkbox>
                      </td>
                      <td
                        data-uipkge=""
                        data-slot="table-cell"
                        class="px-3 py-2 align-middle text-sm whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]"
                      >
                        <span class="text-foreground font-mono text-xs font-semibold hover:underline">${ord.orderNumber}</span>
                      </td>
                      <td
                        data-uipkge=""
                        data-slot="table-cell"
                        class="px-3 py-2 align-middle text-sm whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px] text-muted-foreground text-xs tabular-nums"
                      >
                        ${ord.date}
                      </td>
                      <td
                        data-uipkge=""
                        data-slot="table-cell"
                        class="px-3 py-2 align-middle text-sm whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]"
                      >
                        <div class="flex items-center gap-2.5">
                          <uip-avatar class="size-7">
                            <uip-avatar-fallback class="text-xs font-medium">${initials(ord.customer.name)}</uip-avatar-fallback>
                          </uip-avatar>
                          <div class="min-w-0">
                            <p class="text-foreground truncate text-sm font-medium">${ord.customer.name}</p>
                            <p class="text-muted-foreground truncate text-xs">${ord.customer.email}</p>
                          </div>
                        </div>
                      </td>
                      <td
                        data-uipkge=""
                        data-slot="table-cell"
                        class="px-3 py-2 align-middle text-sm whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]"
                      >
                        <div class="text-xs">
                          <span class="text-foreground font-medium">
                            ${ord.items.reduce((acc, item) => acc + item.quantity, 0)}
                            ${ord.items.reduce((acc, item) => acc + item.quantity, 0) === 1 ? 'item' : 'items'}
                          </span>
                          <span class="text-muted-foreground ml-1.5 inline-block max-w-[180px] truncate align-bottom">
                            • ${ord.items[0]?.name}${ord.items.length > 1 ? ` +${ord.items.length - 1} more` : ''}
                          </span>
                        </div>
                      </td>
                      <td
                        data-uipkge=""
                        data-slot="table-cell"
                        class="px-3 py-2 align-middle text-sm whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px] text-foreground text-right font-mono text-xs font-semibold tabular-nums"
                      >
                        $${formatCurrency(ord.total)}
                      </td>
                      <td
                        data-uipkge=""
                        data-slot="table-cell"
                        class="px-3 py-2 align-middle text-sm whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]"
                      >
                        <uip-badge
                          variant="outline"
                          class=${cn('gap-1.5 px-2 py-0.5 text-xs font-medium capitalize', statusBadgeConfig[ord.status].class)}
                        >
                          <span class=${cn('size-1.5 rounded-full', statusBadgeConfig[ord.status].dot)} aria-hidden="true"></span>
                          ${statusBadgeConfig[ord.status].label}
                        </uip-badge>
                      </td>
                      <td
                        data-uipkge=""
                        data-slot="table-cell"
                        class="px-3 py-2 align-middle text-sm whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px] text-right"
                        @click=${(e: Event) => e.stopPropagation()}
                      >
                        <uip-dropdown-menu class="[&::part(content)]:w-48 [&::part(refund)]:text-destructive">
                          <uip-button slot="trigger" variant="ghost" size="icon-sm" class="text-muted-foreground hover:text-foreground">
                            ${icon(MoreHorizontal, 'ellipsis', 'size-4')}
                            <span class="sr-only">Actions for ${ord.orderNumber}</span>
                          </uip-button>
                          <uip-dropdown-menu-content align="end">
                            <uip-dropdown-menu-label>Order actions</uip-dropdown-menu-label>
                            <uip-dropdown-menu-item @select=${() => this.openOrderDetails(ord)}>
                              ${icon(Eye, 'eye', 'mr-2 size-4')} View details
                            </uip-dropdown-menu-item>
                            <uip-dropdown-menu-item>
                              ${icon(FileText, 'file-text', 'mr-2 size-4')} Download invoice
                            </uip-dropdown-menu-item>
                            ${ord.status === 'processing'
                              ? html`<uip-dropdown-menu-item>
                                  ${icon(Truck, 'truck', 'mr-2 size-4')} Mark as shipped
                                </uip-dropdown-menu-item>`
                              : nothing}
                            <uip-dropdown-menu-separator></uip-dropdown-menu-separator>
                            ${ord.status !== 'refunded'
                              ? html`<uip-dropdown-menu-item part="refund">
                                  ${icon(RotateCcw, 'rotate-ccw', 'mr-2 size-4')} Issue refund
                                </uip-dropdown-menu-item>`
                              : nothing}
                          </uip-dropdown-menu-content>
                        </uip-dropdown-menu>
                      </td>
                    </tr>`,
                  )}
                  ${filteredOrders.length === 0
                    ? html`<tr data-uipkge="" data-slot="table-row" class="border-b transition-colors duration-150">
                        <td
                          data-uipkge=""
                          data-slot="table-cell"
                          colSpan="8"
                          class="px-3 py-2 align-middle text-sm whitespace-nowrap text-muted-foreground h-32 text-center text-sm"
                        >
                          No orders match your filter criteria.
                        </td>
                      </tr>`
                    : nothing}
                </tbody>
              </table>
            </div>
    
            <div class="flex flex-wrap items-center justify-between gap-4">
              <p class="text-muted-foreground text-xs tabular-nums">Showing 1-${filteredOrders.length} of 1,429 orders</p>
              <div class="flex items-center gap-2">
                <uip-button variant="outline" size="sm" class="text-xs" disabled>Previous</uip-button>
                <uip-button variant="outline" size="sm" class="text-xs">Next</uip-button>
              </div>
            </div>
    
            <uip-sheet
              side="right"
              ?open=${this.drawerOpen}
              heading=${order?.orderNumber ?? ''}
              description=${order ? `Placed on ${order.date}` : ''}
              @open-change=${(e: CustomEvent<{ open: boolean }>) => {
                this.drawerOpen = e.detail.open
              }}
            >
              ${order
                ? html`<uip-sheet-body>
                    <div class="flex h-full flex-col">
                      <div class="flex items-center justify-between pr-6">
                        <span class="text-foreground font-mono text-base font-bold">${order.orderNumber}</span>
                        <uip-badge
                          variant="outline"
                          class=${cn('gap-1.5 px-2.5 py-0.5 text-xs font-medium capitalize', statusBadgeConfig[order.status].class)}
                        >
                          <span class=${cn('size-1.5 rounded-full', statusBadgeConfig[order.status].dot)} aria-hidden="true"></span>
                          ${statusBadgeConfig[order.status].label}
                        </uip-badge>
                      </div>
                      <div class="flex-1 space-y-6 overflow-y-auto py-5 text-sm">
                        <div class="bg-muted/20 space-y-3 rounded-lg border p-4">
                          <div class="flex items-center justify-between">
                            <h4 class="text-muted-foreground text-xs font-semibold tracking-wider uppercase">Customer & Shipping</h4>
                            <span class="text-muted-foreground font-mono text-xs">${order.paymentMethod}</span>
                          </div>
                          <div class="flex items-center gap-3">
                            <uip-avatar class="size-9">
                              <uip-avatar-fallback class="text-xs font-semibold">${initials(order.customer.name)}</uip-avatar-fallback>
                            </uip-avatar>
                            <div class="min-w-0">
                              <p class="text-foreground font-medium">${order.customer.name}</p>
                              <p class="text-muted-foreground text-xs">${order.customer.email}</p>
                            </div>
                          </div>
                          <div class="text-muted-foreground border-t pt-2.5 text-xs">
                            <p class="text-foreground font-medium">Delivery address:</p>
                            <p>${order.customer.address}</p>
                            <p>${order.customer.city}, ${order.customer.state} ${order.customer.zip}</p>
                          </div>
                        </div>
    
                        ${order.carrier && order.trackingNumber
                          ? html`<div class="border-border bg-card space-y-2 rounded-lg border p-4">
                              <div class="flex items-center justify-between">
                                <span class="text-muted-foreground text-xs font-semibold tracking-wider uppercase">Fulfillment</span>
                                <uip-badge variant="secondary" class="text-xs font-normal">In Transit</uip-badge>
                              </div>
                              <div class="flex items-center justify-between pt-1 text-xs">
                                <span class="text-muted-foreground">Carrier:</span>
                                <span class="text-foreground font-medium">${order.carrier}</span>
                              </div>
                              <div class="flex items-center justify-between text-xs">
                                <span class="text-muted-foreground">Tracking ID:</span>
                                <span class="text-foreground font-mono font-medium">${order.trackingNumber}</span>
                              </div>
                            </div>`
                          : nothing}
    
                        <div class="space-y-3">
                          <h4 class="text-muted-foreground text-xs font-semibold tracking-wider uppercase">Line Items</h4>
                          <div class="divide-border bg-card divide-y rounded-lg border">
                            ${order.items.map(
                              (item) => html`<div class="flex items-center justify-between gap-3 p-3.5">
                                <div class="min-w-0 flex-1">
                                  <p class="text-foreground truncate text-sm font-medium">${item.name}</p>
                                  <p class="text-muted-foreground font-mono text-xs">
                                    SKU: ${item.sku} • Qty: ${item.quantity} × $${formatCurrency(item.price)}
                                  </p>
                                </div>
                                <div class="text-foreground font-mono text-xs font-semibold tabular-nums">
                                  $${formatCurrency(item.price * item.quantity)}
                                </div>
                              </div>`,
                            )}
                          </div>
                        </div>
    
                        <div class="bg-card space-y-2 rounded-lg border p-4 text-xs">
                          <div class="text-muted-foreground flex justify-between">
                            <span>Subtotal</span>
                            <span class="text-foreground font-mono tabular-nums">$${formatCurrency(order.subtotal)}</span>
                          </div>
                          <div class="text-muted-foreground flex justify-between">
                            <span>Estimated Shipping</span>
                            <span class="text-foreground font-mono tabular-nums"
                              >${order.shipping === 0 ? 'Free' : `$${formatCurrency(order.shipping)}`}</span
                            >
                          </div>
                          <div class="text-muted-foreground flex justify-between">
                            <span>Tax</span>
                            <span class="text-foreground font-mono tabular-nums">$${formatCurrency(order.tax)}</span>
                          </div>
                          <div class="text-foreground flex justify-between border-t pt-2 text-sm font-medium">
                            <span>Total</span>
                            <span class="text-foreground font-mono font-bold tabular-nums">$${formatCurrency(order.total)}</span>
                          </div>
                        </div>
                      </div>
                    </div>
                  </uip-sheet-body>`
                : nothing}
              <uip-button slot="footer" aria-label="Download attachment" variant="outline" size="sm" class="text-xs">
                ${icon(Download, 'download', 'mr-1.5 size-3.5')} Invoice PDF
              </uip-button>
              <uip-button slot="footer" variant="secondary" size="sm" class="text-xs" data-sheet-close>Close</uip-button>
            </uip-sheet>
          </div>
        `
      }
    }
    
    customElements.get('uip-order-management-page') ||
      customElements.define('uip-order-management-page', UipOrderManagementPage)
    
    declare global {
      interface HTMLElementTagNameMap {
        'uip-order-management-page': UipOrderManagementPage
      }
    }
    

Raw manifest:https://uipkge.dev/r/lit/order-management-page.json