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
| Endpoint | Purpose |
|---|---|
GET /api/v1/orders-dashboard | Filtered page + aggregates |
GET /api/v1/orders-dashboard/export | Same filters, up to 5,000 rows |
Query
| Param | Default | Notes |
|---|---|---|
range | 7d | 24h, 7d, 30d, 90d, all. Anything else — including empty — is 7d. No error |
page | 1 | Positive integers only |
limit | 25 | Clamped to 100 (the order list ignores an oversize limit instead) |
sort | -created | Twelve values: created, total_amount, customer_name, source, order_status, payment_status, each with a - desc form. Unknown sort stays -created |
status | order_status | |
source | organization_apps.id | |
payment_status | ||
fulfillment_status | ||
destination | Branch / store id. /orders does not read this | |
notes | has_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=7dThe 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
}