# Order Admin

> An e-commerce orders admin with stat cards, status tabs, a searchable table, pagination and a slide-over detail panel with a status timeline.

- Author: Atelier UI
- License: MIT
- Page: https://chromotion.dev/en/templates/order-admin
- Site: Atelier UI (https://chromotion.dev)

## Install

```bash
npx shadcn@latest add https://chromotion.dev/r/order-admin.json
```

Adds the page at `app/order-admin/page.tsx` together with the components below. Stack: React + TypeScript + Tailwind CSS v4, shadcn/ui conventions. `cn` comes from `@/lib/utils`.

## Components

- [App Sidebar](https://chromotion.dev/r/app-sidebar.md): `@/components/app-sidebar`
- [Drawer](https://chromotion.dev/r/drawer.md): `@/components/drawer`
- [Pagination](https://chromotion.dev/r/pagination.md): `@/components/pagination`
- [Stat Card](https://chromotion.dev/r/stat-card.md): `@/components/stat-card`

## Dependencies

```bash
npm install lucide-react
```

## Source

```tsx title="app/order-admin/page.tsx"
"use client"

import * as React from "react"
import {
  BarChart3,
  Check,
  Download,
  Ellipsis,
  Eye,
  Hexagon,
  LayoutDashboard,
  Package,
  RotateCcw,
  Search,
  Settings,
  ShoppingBag,
  Truck,
  Users,
} from "lucide-react"

import { cn } from "@/lib/utils"
import { AppSidebar } from "@/components/app-sidebar"
import { Drawer } from "@/components/drawer"
import { Pagination } from "@/components/pagination"
import { StatCard } from "@/components/stat-card"

type Status = "pending" | "shipped" | "delivered" | "refunded"
type Filter = "all" | Status

interface Order {
  id: number
  code: string
  customer: string
  email: string
  date: string
  time: string
  status: Status
  lines: { name: string; price: number; qty: number }[]
  subtotal: number
  shipping: number
  total: number
}

const PAGE_SIZE = 8

const catalog = [
  { name: "Atlas leather backpack", price: 148 },
  { name: "Merino crew sweater", price: 96 },
  { name: "Ceramic pour-over set", price: 64 },
  { name: "Trail running shoes", price: 129 },
  { name: "Linen overshirt", price: 84 },
  { name: "Insulated bottle 750 ml", price: 32 },
  { name: "Canvas tote", price: 28 },
  { name: "Wool beanie", price: 24 },
]

// id, customer, email, date, status, [catalog index, quantity][], time
const raw: [number, string, string, string, Status, [number, number][], string][] = [
  [10482, "Priya Raman", "priya@northwind.co", "2026-11-06", "pending", [[0, 1], [5, 2]], "9:14 AM"],
  [10481, "Marcus Lee", "marcus@hexa.dev", "2026-11-06", "pending", [[3, 1]], "8:02 AM"],
  [10480, "Sofia Alvarez", "sofia@brightlabs.io", "2026-11-05", "shipped", [[1, 1], [7, 1]], "6:47 PM"],
  [10479, "Jonas Weber", "jonas@kestrel.studio", "2026-11-05", "shipped", [[2, 1], [5, 1]], "2:31 PM"],
  [10478, "Aiko Tanaka", "aiko@orbit.jp", "2026-11-05", "delivered", [[4, 2]], "11:20 AM"],
  [10477, "Daniel Okafor", "daniel@lumen.app", "2026-11-04", "pending", [[0, 1]], "10:05 PM"],
  [10476, "Elena Petrova", "elena@fjord.no", "2026-11-04", "shipped", [[6, 3], [5, 1]], "4:18 PM"],
  [10475, "Tomás Herrera", "tomas@sol.mx", "2026-11-04", "delivered", [[1, 1]], "9:44 AM"],
  [10474, "Chloe Martin", "chloe@maison.fr", "2026-11-03", "refunded", [[3, 1]], "7:12 PM"],
  [10473, "Ravi Patel", "ravi@spectra.in", "2026-11-03", "delivered", [[2, 2], [7, 2]], "1:55 PM"],
  [10472, "Hannah Becker", "hannah@alpine.de", "2026-11-03", "delivered", [[0, 1], [4, 1]], "8:30 AM"],
  [10471, "Liam O'Connor", "liam@harbor.ie", "2026-11-02", "shipped", [[5, 4]], "5:09 PM"],
  [10470, "Mei Chen", "mei@lotus.sg", "2026-11-02", "delivered", [[1, 2]], "3:26 PM"],
  [10469, "Noah Williams", "noah@summit.us", "2026-11-02", "pending", [[3, 1], [6, 1]], "12:48 PM"],
  [10468, "Isabella Rossi", "isabella@vento.it", "2026-11-01", "delivered", [[4, 1], [7, 1]], "10:37 AM"],
  [10467, "Kwame Mensah", "kwame@accra.gh", "2026-11-01", "delivered", [[0, 1]], "9:03 AM"],
  [10466, "Olivia Brown", "olivia@meadow.uk", "2026-10-31", "refunded", [[2, 1]], "6:21 PM"],
  [10465, "Lucas Silva", "lucas@praia.br", "2026-10-31", "delivered", [[3, 2]], "2:14 PM"],
  [10464, "Yuki Sato", "yuki@sakura.jp", "2026-10-31", "shipped", [[1, 1], [6, 2]], "11:59 AM"],
  [10463, "Amara Nwosu", "amara@lagos.ng", "2026-10-30", "delivered", [[5, 2], [7, 3]], "4:40 PM"],
  [10462, "Felix Müller", "felix@rhein.de", "2026-10-30", "delivered", [[4, 1]], "1:07 PM"],
  [10461, "Zoe Campbell", "zoe@tide.au", "2026-10-30", "pending", [[2, 1], [5, 1]], "9:52 AM"],
  [10460, "Omar Haddad", "omar@souk.ae", "2026-10-29", "delivered", [[0, 1], [3, 1]], "7:33 PM"],
  [10459, "Grace Kim", "grace@nori.kr", "2026-10-29", "refunded", [[1, 1]], "3:02 PM"],
  [10458, "Ethan Clarke", "ethan@cove.ca", "2026-10-29", "delivered", [[6, 2]], "10:16 AM"],
  [10457, "Nina Kowalski", "nina@wisla.pl", "2026-10-28", "delivered", [[4, 2], [5, 1]], "8:44 AM"],
]

const initialOrders: Order[] = raw.map(([id, customer, email, date, status, picks, time]) => {
  const lines = picks.map(([index, qty]) => ({ ...catalog[index], qty }))
  const subtotal = lines.reduce((sum, line) => sum + line.price * line.qty, 0)
  const shipping = subtotal >= 100 ? 0 : 8
  return { id, code: `HX-${id}`, customer, email, date, time, status, lines, subtotal, shipping, total: subtotal + shipping }
})

const months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]

function formatDate(iso: string) {
  const [year, month, day] = iso.split("-").map(Number)
  return `${months[month - 1]} ${day}, ${year}`
}

const money = (value: number) => `$${value.toFixed(2)}`

const statusStyle: Record<Status, string> = {
  pending: "bg-amber-500/10 text-amber-700 dark:text-amber-300",
  shipped: "bg-blue-500/10 text-blue-700 dark:text-blue-300",
  delivered: "bg-emerald-500/10 text-emerald-700 dark:text-emerald-300",
  refunded: "bg-rose-500/10 text-rose-700 dark:text-rose-300",
}

const filters: { id: Filter; label: string }[] = [
  { id: "all", label: "All" },
  { id: "pending", label: "Pending" },
  { id: "shipped", label: "Shipped" },
  { id: "delivered", label: "Delivered" },
  { id: "refunded", label: "Refunded" },
]

function StatusBadge({ status }: { status: Status }) {
  return (
    <span className={cn("inline-flex items-center gap-1.5 rounded-full px-2.5 py-0.5 text-xs font-medium capitalize", statusStyle[status])}>
      <span aria-hidden className="size-1.5 rounded-full bg-current" />
      {status}
    </span>
  )
}

const nextStep: Partial<Record<Status, { to: Status; label: string; icon: React.ComponentType<{ className?: string }> }>> = {
  pending: { to: "shipped", label: "Mark as shipped", icon: Truck },
  shipped: { to: "delivered", label: "Mark as delivered", icon: Package },
}

const menuItem =
  "flex h-8 w-full items-center gap-2 rounded-md px-2 text-left text-sm outline-none hover:bg-accent focus-visible:bg-accent disabled:pointer-events-none disabled:opacity-50"

function RowMenu({
  order,
  onView,
  onStatus,
}: {
  order: Order
  onView: () => void
  onStatus: (status: Status) => void
}) {
  const [position, setPosition] = React.useState<{ top?: number; bottom?: number; right: number } | null>(null)
  const root = React.useRef<HTMLDivElement>(null)
  const trigger = React.useRef<HTMLButtonElement>(null)
  const menu = React.useRef<HTMLDivElement>(null)
  const menuId = React.useId()
  const isOpen = position !== null
  const step = nextStep[order.status]

  React.useEffect(() => {
    if (!isOpen) return
    const close = () => setPosition(null)
    const onPointerDown = (event: PointerEvent) => {
      if (!root.current?.contains(event.target as Node)) close()
    }
    const onKeyDown = (event: KeyboardEvent) => {
      if (event.key !== "Escape") return
      close()
      trigger.current?.focus()
    }
    // The menu is fixed-positioned so the table's scroll area cannot clip it; follow the page instead
    window.addEventListener("scroll", close, true)
    window.addEventListener("resize", close)
    document.addEventListener("pointerdown", onPointerDown)
    document.addEventListener("keydown", onKeyDown)
    return () => {
      window.removeEventListener("scroll", close, true)
      window.removeEventListener("resize", close)
      document.removeEventListener("pointerdown", onPointerDown)
      document.removeEventListener("keydown", onKeyDown)
    }
  }, [isOpen])

  const open = () => {
    const rect = trigger.current?.getBoundingClientRect()
    if (!rect) return
    const right = document.documentElement.clientWidth - rect.right
    const fitsBelow = window.innerHeight - rect.bottom > 190
    setPosition(fitsBelow ? { top: rect.bottom + 4, right } : { bottom: window.innerHeight - rect.top + 4, right })
    requestAnimationFrame(() => menu.current?.querySelector<HTMLElement>("[role=menuitem]:not(:disabled)")?.focus({ preventScroll: true }))
  }

  const choose = (action: () => void) => {
    setPosition(null)
    action()
  }

  const onMenuKeyDown = (event: React.KeyboardEvent) => {
    const items = [...(menu.current?.querySelectorAll<HTMLElement>("[role=menuitem]:not(:disabled)") ?? [])]
    const index = items.indexOf(document.activeElement as HTMLElement)
    if (event.key === "ArrowDown") items[(index + 1) % items.length]?.focus()
    else if (event.key === "ArrowUp") items[(index - 1 + items.length) % items.length]?.focus()
    else if (event.key === "Tab") setPosition(null)
    else return
    if (event.key !== "Tab") event.preventDefault()
  }

  return (
    <div ref={root} className="inline-block">
      <button
        ref={trigger}
        type="button"
        aria-label={`Actions for order ${order.code}`}
        aria-haspopup="menu"
        aria-expanded={isOpen}
        aria-controls={isOpen ? menuId : undefined}
        onClick={() => (isOpen ? setPosition(null) : open())}
        className="flex size-8 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-accent hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none aria-expanded:bg-accent"
      >
        <Ellipsis className="size-4" />
      </button>
      {isOpen && (
        <div
          ref={menu}
          id={menuId}
          role="menu"
          aria-label={`Order ${order.code}`}
          onKeyDown={onMenuKeyDown}
          style={position}
          className="fixed z-50 w-48 rounded-xl border bg-popover p-1 text-popover-foreground shadow-lg animate-in fade-in zoom-in-95"
        >
          <button type="button" role="menuitem" onClick={() => choose(onView)} className={menuItem}>
            <Eye className="size-4 opacity-70" />
            View details
          </button>
          {step && (
            <button type="button" role="menuitem" onClick={() => choose(() => onStatus(step.to))} className={menuItem}>
              <step.icon className="size-4 opacity-70" />
              {step.label}
            </button>
          )}
          <button
            type="button"
            role="menuitem"
            disabled={order.status === "refunded"}
            onClick={() => choose(() => onStatus("refunded"))}
            className={cn(menuItem, "text-destructive hover:bg-destructive/10 focus-visible:bg-destructive/10")}
          >
            <RotateCcw className="size-4 opacity-70" />
            Refund order
          </button>
        </div>
      )}
    </div>
  )
}

function Timeline({ order }: { order: Order }) {
  const { status } = order
  const tracking = `HXT-${order.id * 7}`
  const steps: { title: string; detail: string; state: "done" | "current" | "todo" }[] = [
    { title: "Order placed", detail: `${formatDate(order.date)} · ${order.time}`, state: "done" },
    status === "pending"
      ? { title: "Awaiting payment", detail: "Bank transfer not received yet", state: "current" }
      : { title: "Payment confirmed", detail: "Card ending 4242", state: "done" },
  ]
  if (status === "refunded") {
    steps.push({ title: "Refund issued", detail: `${money(order.total)} returned to the original payment method`, state: "current" })
  } else {
    steps.push({
      title: "Shipped",
      detail: status === "pending" ? "Ships once payment clears" : `Tracking ${tracking}`,
      state: status === "shipped" ? "current" : status === "delivered" ? "done" : "todo",
    })
    steps.push({
      title: "Delivered",
      detail: status === "delivered" ? "Left at the front door" : "Estimated in 3 to 5 days",
      state: status === "delivered" ? "current" : "todo",
    })
  }

  return (
    <ol className="flex flex-col">
      {steps.map((step, index) => (
        <li key={step.title} aria-current={step.state === "current" ? "step" : undefined} className="relative flex gap-3 pb-5 last:pb-0">
          {index < steps.length - 1 && (
            <span aria-hidden className={cn("absolute top-6 bottom-0 left-3 w-px -translate-x-1/2", step.state === "done" ? "bg-primary" : "bg-border")} />
          )}
          <span
            aria-hidden
            className={cn(
              "z-10 flex size-6 shrink-0 items-center justify-center rounded-full border-2 bg-background",
              step.state === "done" && "border-primary bg-primary text-primary-foreground",
              step.state === "current" && "border-primary",
              step.state === "todo" && "border-border"
            )}
          >
            {step.state === "done" ? <Check className="size-3.5" /> : step.state === "current" ? <span className="size-2 rounded-full bg-primary" /> : null}
          </span>
          <div className="min-w-0 pt-0.5">
            <p className={cn("text-sm font-medium", step.state === "todo" && "text-muted-foreground")}>{step.title}</p>
            <p className="text-xs text-muted-foreground">{step.detail}</p>
          </div>
        </li>
      ))}
    </ol>
  )
}

function OrderDetail({ order, onStatus }: { order: Order; onStatus: (status: Status) => void }) {
  return (
    <div className="flex flex-col gap-6">
      <div className="flex items-center justify-between gap-3">
        <StatusBadge status={order.status} />
        <p className="text-sm text-muted-foreground">
          {formatDate(order.date)} · {order.time}
        </p>
      </div>

      <section aria-labelledby="detail-customer">
        <h3 id="detail-customer" className="mb-2 text-xs font-medium tracking-wide text-muted-foreground uppercase">
          Customer
        </h3>
        <div className="flex items-center gap-3 rounded-xl border p-3">
          <span aria-hidden className="flex size-10 shrink-0 items-center justify-center rounded-full bg-linear-to-br from-indigo-500 to-violet-500 text-xs font-semibold text-white">
            {order.customer
              .split(" ")
              .map((part) => part[0])
              .join("")
              .slice(0, 2)}
          </span>
          <div className="min-w-0">
            <p className="truncate text-sm font-medium">{order.customer}</p>
            <p className="truncate text-xs text-muted-foreground">{order.email}</p>
          </div>
        </div>
      </section>

      <section aria-labelledby="detail-items">
        <h3 id="detail-items" className="mb-2 text-xs font-medium tracking-wide text-muted-foreground uppercase">
          Items
        </h3>
        <ul className="divide-y rounded-xl border">
          {order.lines.map((line) => (
            <li key={line.name} className="flex items-center gap-3 p-3">
              <span aria-hidden className="flex size-10 shrink-0 items-center justify-center rounded-lg bg-muted text-muted-foreground">
                <Package className="size-4" />
              </span>
              <div className="min-w-0 flex-1">
                <p className="truncate text-sm font-medium">{line.name}</p>
                <p className="text-xs text-muted-foreground">
                  {line.qty} × {money(line.price)}
                </p>
              </div>
              <p className="text-sm tabular-nums">{money(line.price * line.qty)}</p>
            </li>
          ))}
        </ul>
        <dl className="mt-3 flex flex-col gap-1.5 px-1 text-sm">
          <div className="flex justify-between text-muted-foreground">
            <dt>Subtotal</dt>
            <dd className="tabular-nums">{money(order.subtotal)}</dd>
          </div>
          <div className="flex justify-between text-muted-foreground">
            <dt>Shipping</dt>
            <dd className="tabular-nums">{order.shipping === 0 ? "Free" : money(order.shipping)}</dd>
          </div>
          <div className="flex justify-between border-t pt-2 font-semibold">
            <dt>Total</dt>
            <dd className="tabular-nums">{money(order.total)}</dd>
          </div>
        </dl>
      </section>

      <section aria-labelledby="detail-timeline">
        <h3 id="detail-timeline" className="mb-3 text-xs font-medium tracking-wide text-muted-foreground uppercase">
          Status timeline
        </h3>
        <Timeline order={order} />
      </section>

      <div className="flex flex-wrap gap-2 border-t pt-4">
        {nextStep[order.status] && (
          <button
            type="button"
            onClick={() => onStatus(nextStep[order.status]!.to)}
            className="inline-flex h-9 items-center gap-2 rounded-lg bg-primary px-4 text-sm font-medium text-primary-foreground transition-opacity hover:opacity-90 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background focus-visible:outline-none"
          >
            {nextStep[order.status]!.label}
          </button>
        )}
        <button
          type="button"
          disabled={order.status === "refunded"}
          onClick={() => onStatus("refunded")}
          className="inline-flex h-9 items-center gap-2 rounded-lg border bg-background px-4 text-sm font-medium text-destructive shadow-xs transition-colors hover:bg-destructive/10 focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50"
        >
          <RotateCcw className="size-4" />
          Refund order
        </button>
      </div>
    </div>
  )
}

export default function OrderAdmin() {
  const searchId = React.useId()
  const tabsId = React.useId()
  const [orders, setOrders] = React.useState<Order[]>(initialOrders)
  const [filter, setFilter] = React.useState<Filter>("all")
  const [query, setQuery] = React.useState("")
  const [page, setPage] = React.useState(1)
  const [detailId, setDetailId] = React.useState<number | null>(null)
  const [detailOpen, setDetailOpen] = React.useState(false)
  const tabList = React.useRef<HTMLDivElement>(null)

  const counts: Record<Filter, number> = {
    all: orders.length,
    pending: orders.filter((o) => o.status === "pending").length,
    shipped: orders.filter((o) => o.status === "shipped").length,
    delivered: orders.filter((o) => o.status === "delivered").length,
    refunded: orders.filter((o) => o.status === "refunded").length,
  }

  const needle = query.trim().toLowerCase()
  const matches = orders.filter(
    (o) =>
      (filter === "all" || o.status === filter) &&
      (!needle || [o.code, o.customer, o.email].some((text) => text.toLowerCase().includes(needle)))
  )
  const pageCount = Math.max(1, Math.ceil(matches.length / PAGE_SIZE))
  const currentPage = Math.min(page, pageCount)
  const rows = matches.slice((currentPage - 1) * PAGE_SIZE, currentPage * PAGE_SIZE)
  const detail = orders.find((o) => o.id === detailId) ?? null

  const setStatus = (id: number, status: Status) =>
    setOrders((all) => all.map((o) => (o.id === id ? { ...o, status } : o)))
  const view = (id: number) => {
    setDetailId(id)
    setDetailOpen(true)
  }
  const chooseFilter = (next: Filter) => {
    setFilter(next)
    setPage(1)
  }

  const onTabKeyDown = (event: React.KeyboardEvent) => {
    const index = filters.findIndex((f) => f.id === filter)
    let next = index
    if (event.key === "ArrowRight") next = (index + 1) % filters.length
    else if (event.key === "ArrowLeft") next = (index - 1 + filters.length) % filters.length
    else if (event.key === "Home") next = 0
    else if (event.key === "End") next = filters.length - 1
    else return
    event.preventDefault()
    chooseFilter(filters[next].id)
    tabList.current?.querySelectorAll<HTMLElement>("[role=tab]")[next]?.focus()
  }

  return (
    <AppSidebar
      collapseBelow={768}
      brand={
        <>
          <Hexagon className="size-5 shrink-0 fill-primary text-primary" /> Hexa Shop
        </>
      }
      brandIcon={<Hexagon className="size-5 shrink-0 fill-primary text-primary" />}
      groups={[
        {
          items: [
            { label: "Dashboard", href: "#", icon: <LayoutDashboard /> },
            { label: "Orders", href: "#", icon: <ShoppingBag />, active: true, badge: 5 },
            { label: "Products", href: "#", icon: <Package /> },
            { label: "Customers", href: "#", icon: <Users /> },
          ],
        },
        {
          label: "Insights",
          items: [
            { label: "Analytics", href: "#", icon: <BarChart3 /> },
            { label: "Settings", href: "#", icon: <Settings /> },
          ],
        },
      ]}
      user={{ name: "Ada Lovelace", email: "ada@hexa.dev", initials: "AL" }}
      accentColor="var(--ring)"
    >
      <div className="flex flex-wrap items-center justify-between gap-3 border-b bg-background px-4 py-4 sm:px-6">
        <div className="min-w-0">
          <h1 className="text-xl font-semibold tracking-tight">Orders</h1>
          <p className="text-sm text-muted-foreground">Track, fulfil and refund customer orders.</p>
        </div>
        <button
          type="button"
          className="inline-flex h-9 items-center gap-2 rounded-lg border bg-background px-3 text-sm font-medium shadow-xs transition-colors hover:bg-accent focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none"
        >
          <Download className="size-4" />
          Export CSV
        </button>
      </div>

      <div className="flex flex-col gap-6 p-4 sm:p-6">
        <section aria-label="Order statistics" className="grid gap-4 sm:grid-cols-2 xl:grid-cols-4">
          <StatCard className="max-w-none" label="Revenue" value="$48,290" change={12.4} caption="vs. last 30 days" data={[31, 34, 33, 38, 41, 40, 44, 48]} />
          <StatCard className="max-w-none" label="Orders" value="1,284" change={8.1} caption="vs. last 30 days" data={[900, 960, 940, 1010, 1100, 1150, 1210, 1284]} />
          <StatCard className="max-w-none" label="Conversion" value="3.42%" change={0.6} caption="vs. last 30 days" data={[2.8, 3.0, 2.9, 3.1, 3.2, 3.1, 3.3, 3.42]} />
          <StatCard className="max-w-none" label="Avg. order" value="$86.20" change={-2.3} caption="vs. last 30 days" data={[92, 90, 91, 89, 88, 90, 87, 86.2]} />
        </section>

        <section aria-label="Orders list" className="flex min-w-0 flex-col gap-4">
          <div className="flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between">
            <div className="relative -mx-1 overflow-x-auto px-1">
              <div
                ref={tabList}
                role="tablist"
                aria-label="Filter orders by status"
                onKeyDown={onTabKeyDown}
                className="flex w-max gap-1 rounded-lg bg-muted p-1"
              >
                {filters.map((item) => {
                  const selected = filter === item.id
                  return (
                    <button
                      key={item.id}
                      id={`${tabsId}-${item.id}`}
                      type="button"
                      role="tab"
                      aria-selected={selected}
                      aria-controls={`${tabsId}-panel`}
                      tabIndex={selected ? 0 : -1}
                      onClick={() => chooseFilter(item.id)}
                      className="inline-flex h-8 items-center gap-2 rounded-md px-3 text-sm font-medium whitespace-nowrap text-muted-foreground transition-colors hover:text-foreground focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none aria-selected:bg-background aria-selected:text-foreground aria-selected:shadow-sm"
                    >
                      {item.label}
                      <span className="rounded-full bg-foreground/10 px-1.5 text-xs text-foreground tabular-nums">{counts[item.id]}</span>
                    </button>
                  )
                })}
              </div>
            </div>
            <form role="search" onSubmit={(event) => event.preventDefault()} className="relative w-full lg:max-w-xs">
              <label htmlFor={searchId} className="sr-only">
                Search orders
              </label>
              <Search aria-hidden className="pointer-events-none absolute top-1/2 left-3 size-4 -translate-y-1/2 text-muted-foreground" />
              <input
                id={searchId}
                type="search"
                value={query}
                onChange={(event) => {
                  setQuery(event.target.value)
                  setPage(1)
                }}
                placeholder="Search orders or customers"
                autoComplete="off"
                className="h-9 w-full rounded-lg border bg-background pr-3 pl-9 text-sm shadow-xs outline-none transition-[border-color,box-shadow] placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/20"
              />
            </form>
          </div>

          <div
            role="tabpanel"
            id={`${tabsId}-panel`}
            aria-labelledby={`${tabsId}-${filter}`}
            className="relative overflow-x-auto rounded-2xl border bg-card"
          >
            <table className="w-full min-w-[540px] text-left text-sm">
              <caption className="sr-only">Orders</caption>
              <thead>
                <tr className="border-b bg-muted/40 text-xs text-muted-foreground">
                  <th scope="col" className="px-4 py-3 font-medium">Order</th>
                  <th scope="col" className="px-4 py-3 font-medium">Customer</th>
                  <th scope="col" className="hidden px-4 py-3 font-medium md:table-cell">Date</th>
                  <th scope="col" className="px-4 py-3 text-right font-medium">Total</th>
                  <th scope="col" className="px-4 py-3 font-medium">Status</th>
                  <th scope="col" className="w-12 px-2 py-3">
                    <span className="sr-only">Actions</span>
                  </th>
                </tr>
              </thead>
              <tbody>
                {rows.length === 0 ? (
                  <tr>
                    <td colSpan={6} className="px-4 py-14 text-center">
                      <p className="text-sm font-medium">No orders found</p>
                      <p className="mt-1 text-sm text-muted-foreground">Try another status or search term.</p>
                    </td>
                  </tr>
                ) : (
                  rows.map((order) => (
                    <tr key={order.id} className="border-b last:border-0 hover:bg-accent/40">
                      <td className="px-4 py-3 whitespace-nowrap">
                        <button
                          type="button"
                          onClick={() => view(order.id)}
                          aria-label={`View order ${order.code}`}
                          className="rounded-md font-medium underline-offset-4 hover:underline focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none"
                        >
                          {order.code}
                        </button>
                      </td>
                      <td className="max-w-0 px-4 py-3">
                        <p className="truncate font-medium">{order.customer}</p>
                        <p className="truncate text-xs text-muted-foreground">{order.email}</p>
                      </td>
                      <td className="hidden px-4 py-3 whitespace-nowrap text-muted-foreground md:table-cell">{formatDate(order.date)}</td>
                      <td className="px-4 py-3 text-right whitespace-nowrap tabular-nums">{money(order.total)}</td>
                      <td className="px-4 py-3 whitespace-nowrap">
                        <StatusBadge status={order.status} />
                      </td>
                      <td className="px-2 py-3 text-right">
                        <RowMenu order={order} onView={() => view(order.id)} onStatus={(status) => setStatus(order.id, status)} />
                      </td>
                    </tr>
                  ))
                )}
              </tbody>
            </table>
          </div>

          <p role="status" className="sr-only">
            {matches.length} {matches.length === 1 ? "order" : "orders"} found
          </p>
          <Pagination
            totalItems={matches.length}
            pageSize={PAGE_SIZE}
            page={currentPage}
            onPageChange={setPage}
            siblingCount={0}
            className="justify-center sm:justify-between"
          />
        </section>
      </div>

      <Drawer
        open={detailOpen}
        onOpenChange={setDetailOpen}
        side="right"
        size={440}
        title={detail ? `Order ${detail.code}` : "Order"}
        description={detail ? `${detail.customer} · ${money(detail.total)}` : undefined}
      >
        {detail && <OrderDetail order={detail} onStatus={(status) => setStatus(detail.id, status)} />}
      </Drawer>
    </AppSidebar>
  )
}
```
