# Admin can filter orders by status, date range, or total amount

- **Pattern:** `ab-001152` (`ecommerce-order-management.admin-management.order-filtering`)
- **Severity:** low
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-001152
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-001152)

## Why it matters

Search by identifier (handled by `order-search`) and structured filtering are complementary and both required. An admin who can find a specific order by ID but cannot filter to 'all pending orders from the last 7 days' cannot manage their queue proactively — they can only react to inbound support contacts. iso-25010:2011 operability requires that the system supports the work patterns of its operators. For order operations, that means at minimum: filter by status (to work a specific queue) and filter by date range (to find orders placed in a given period). Without server-side filtering, a client-side filter on a paginated list gives false confidence while silently excluding records off the first page.

## Severity rationale

Low because the absence of structured filters degrades admin efficiency but does not prevent order processing — admins can still use search and pagination to manage orders manually.

## Remediation

Add `status`, `from`, `to`, `minTotal`, and `maxTotal` filter parameters to the admin orders API at `app/api/admin/orders/route.ts` and apply them server-side.

```ts
// app/api/admin/orders/route.ts
const { searchParams } = new URL(req.url)
const status   = searchParams.get('status')
const from     = searchParams.get('from')
const to       = searchParams.get('to')
const minTotal = searchParams.get('minTotal')
const maxTotal = searchParams.get('maxTotal')

const where: Prisma.OrderWhereInput = {}
if (status) where.status = status
if (from || to) where.createdAt = {
  ...(from ? { gte: new Date(from) } : {}),
  ...(to   ? { lte: new Date(to)   } : {}),
}
if (minTotal || maxTotal) where.total = {
  ...(minTotal ? { gte: parseInt(minTotal) } : {}),
  ...(maxTotal ? { lte: parseInt(maxTotal) } : {}),
}
```

Render a status dropdown and a date range picker in the admin orders UI, both wired to query parameters.

## Detection

- **ID:** `order-filtering`
- **Severity:** `low`
- **What to look for:** Check the admin orders list for filter controls beyond search. Count the number of distinct filter types available — enumerate each one found: status filter (dropdown or checkbox group), date range picker, total amount range filter, or others. At least 2 filter types are required. Examine the backing API endpoint or query to verify that the filter parameters are actually applied to the database query — not just used for client-side filtering. Quote the exact query parameter names used (e.g., `?status=`, `?from=`, `?to=`).
- **Pass criteria:** The admin orders list includes at least 2 of these 3 filter types: status filter, date range filter, or total amount filter. The filters are applied server-side to the database query, not client-side on a pre-loaded list. Selected filters produce a meaningfully narrowed results set. A single filter type alone (e.g., only status) does not count as pass — at least 2 are required.
- **Fail criteria:** The admin order list has fewer than 2 filtering controls — only a full unfiltered list (with optional search). Admins cannot narrow the list to, for example, only `pending` orders from the past week.
- **Skip (N/A) when:** The project has no admin section. No admin orders list page exists in the codebase.
- **Detail on fail:** `"The admin orders list at /admin/orders has 0 filter controls. It displays all orders in reverse chronological order with no way to filter by status, date, or amount. For stores with more than a few dozen orders this becomes unusable."`
- **Cross-reference:** The `order-search` check evaluates search by identifier; this check evaluates structured filtering. Both are needed for a functional admin experience — one without the other leaves gaps.
- **Remediation:** Add filter parameters to your admin orders API (`app/api/admin/orders/route.ts`) and filter controls to the UI:

  ```ts
  // app/api/admin/orders/route.ts
  export async function GET(req: Request) {
    const { searchParams } = new URL(req.url)
    const status = searchParams.get('status')
    const from   = searchParams.get('from')
    const to     = searchParams.get('to')
    const minTotal = searchParams.get('minTotal')
    const maxTotal = searchParams.get('maxTotal')

    const where: Prisma.OrderWhereInput = {}
    if (status) where.status = status
    if (from || to) {
      where.createdAt = {
        ...(from ? { gte: new Date(from) } : {}),
        ...(to   ? { lte: new Date(to)   } : {}),
      }
    }
    if (minTotal || maxTotal) {
      where.total = {
        ...(minTotal ? { gte: parseInt(minTotal) } : {}),
        ...(maxTotal ? { lte: parseInt(maxTotal) } : {}),
      }
    }

    const orders = await db.orders.findMany({ where, orderBy: { createdAt: 'desc' }, take: 100 })
    return Response.json({ orders })
  }
  ```

---

## External references

- iso-25010 operability

Taxons: observability

HTML version: https://auditbuffet.com/patterns/ab-001152
