Linkit

Orders dashboard

One filtered page plus KPIs, heatmap, and export — the admin Orders screen

Orders dashboard

GET /api/v1/orders is a list. The admin Orders screen reads a sibling path because /api/v1/orders/dashboard would be served as "get the order whose id is dashboard".

Both routes are mounted (repocommerce::PgOrdersDashboard). Live merchant JWT, 2026-09-10: 200. Home used to claim this was 501; the flag is mounted. Do not assemble the screen from /orders + /orders/analytics/summary again — those two have different range semantics.

Requires a tenant Bearer token (list × order). The dashboard forwards column names (source, destination, created), not the /orders aliases (organization_app_id, destination_id, created_at).


Quick reference

EndpointPurpose
GET /api/v1/orders-dashboardFiltered page + aggregates
GET /api/v1/orders-dashboard/exportSame filters, up to 5,000 rows

Query

ParamDefaultNotes
range7d24h, 7d, 30d, 90d, all. Anything else — including empty — is 7d. No error
page1Positive integers only
limit25Clamped to 100 (the order list ignores an oversize limit instead)
sort-createdTwelve values: created, total_amount, customer_name, source, order_status, payment_status, each with a - desc form. Unknown sort stays -created
statusorder_status
sourceorganization_apps.id
payment_status
fulfillment_status
destinationBranch / store id. /orders does not read this
noteshas_notes or no_notes; anything else applies no filter
search

range=all leaves from empty and hours at -1. Other ranges set from to the same space-separated datetime rendering the created column stores, so the text comparison is correct.


Dashboard body

Live empty-tenant example (keys only; heatmap is always 7 × 24, Monday-first, Riyadh +03):

{
  "range": "7d",
  "from": "2026-09-03 14:21:50.381Z",
  "page": 1,
  "per_page": 1,
  "total_filtered": 0,
  "total_pages": 1,
  "items": [],
  "aggregates": {
    "kpis": {
      "total_orders": 0,
      "gross_revenue": 0,
      "average_order_value": 0,
      "pending_count": 0,
      "cancelled_count": 0,
      "fulfilled_count": 0,
      "currency": "SAR"
    },
    "status_rows": [],
    "source_stats": [],
    "unique_sources": [],
    "unique_destinations": [],
    "heatmap": [],
    "branch_perf": [],
    "destination_aggregates": [],
    "urgency": [],
    "total_in_range": 0
  },
  "generated_at": "2026-09-10T14:21:50Z"
}

urgency is at most 15 rows. currency on KPIs is MAX(currency) over the range, default SAR.


Export

GET /api/v1/orders-dashboard/export?range=7d

The 5,000 cap overrides the 100-row clamp. truncated is total > len(items) — hitting the cap exactly reports false.

Live empty-tenant:

{
  "range": "7d",
  "from": "2026-09-03 14:21:50.401Z",
  "organization_id": "0rmzqwt5hhaf07r",
  "items": [],
  "total": 0,
  "truncated": false
}