# Dashboard — Contracts

> Produced by the Reversa **Writer** (phase: generation) · doc_level: `complete`
> Generated on 2026-09-19

**Confidence scale:** 🟢 CONFIRMED · 🟡 INFERRED · 🔴 GAP

External HTTP contract exposed by the `dashboard` unit. The route sits at the site root (admin prefix is empty) behind the `['web','admin']` middleware group. The response is a **server-rendered HTML page** (`pages.dashboard`) — this is a session-cookie web app, not a JSON API. The controller is `App\Http\Controllers\DashboardController`. 🟢 (`routes/web.php:42`, `DashboardController.php:121`)

---

## GET `/dashboard` — Analytics dashboard 🟢

- **Auth:** required (`admin` middleware); anonymous → `302` to `auth/login`. 🟢 (`config/admin.php:29`, `routes/web.php:24-28`)
- **Request (query string):**

  | Field | Type | Required | Notes |
  |-------|------|----------|-------|
  | `range` | string enum `day` \| `week` \| `month` | ❌ | Chart bucketing; defaults to `month` when absent, falsy, or any other value. ✅ Fixed (2026-09-19): previously an out-of-whitelist value produced a broken query (no `default` case). 🟢 (`:47-50`) |

- **Behavior:** computes three live KPIs, a sales-volume chart bucketed per `range`, and a top-10 best-selling-products list, then renders `pages.dashboard`. Nothing is written; the request is idempotent and side-effect-free. 🟢 (`:28-122`)
- **Responses:**
  - `200 OK` — HTML dashboard. The Blade view receives the payload below.
  - `302 Found` — `Location: auth/login` when unauthenticated.
- Source: `DashboardController::index` (`:28-122`).

### View payload (server→template, not wire JSON) 🟢 (`:121`)

| Variable | Type | Meaning |
|----------|------|---------|
| `total` | `{products:int, customers:int, orders:int}` | KPI counters. `orders` = today's `status='done'` orders. |
| `chartRange` | `{day:"Day", week:"Week", month:"Month"}` | Fixed range-selector labels. |
| `range` | `string` | Active range echoed back to the view. |
| `yearRange` | `string\|null` | Year(s) spanned by a month chart (e.g. `"2026"`); `null` for day/week. |
| `labelChart` | `string[]` | Ordered x-axis bucket labels. |
| `valueChart` | `int[]` | Order counts aligned index-for-index with `labelChart` (empty buckets = `0`). |
| `products` | `Array<{id:int, name:string, quantity:int}>` | Top-10 products by summed sold quantity (≤10, desc; soft-deleted excluded). |

### Bucketing contract by `range` 🟢

| `range` | Buckets (labels) | Window per bucket | Extra |
|---------|------------------|-------------------|-------|
| `day` | `Sáng`, `Trưa`, `Chiều` (exactly 3) | 06:00–12:00 / 12:00–18:00 / 18:00–24:00 (00:00–06:00 excluded) | — |
| `week` | one per calendar day, ISO-date labels | `DATE(created_at) = <day>` for `startOfWeek..endOfWeek` | — |
| `month` | 6-day windows, `dd/mm-dd/mm` labels | `[windowStart, windowEnd]` inclusive; windows tile the month with no gap. ✅ Fixed 2026-09-22 (was 5-day exclusive windows with a 1-day gap dropping every 6th day — see requirements.md BR-05). A separate month-boundary spillover for non-6-multiple month lengths remains open. | `yearRange` populated |

- Bucket **value** is `COUNT(*)` of `orders` in the window — a count of orders, **not** revenue or item quantity. 🟢 (`:59,69,84,102`)

---

## Cross-cutting contract notes

- **Method surface:** although registered via `resource('/dashboard', …)`, only `GET /dashboard` (`index`) is meaningful; `POST/PUT/PATCH/DELETE` and `show/create/edit` are unused framework stubs and are **out of scope** — do not expose them. 🟢 (`routes/web.php:42`)
- **Transport / session:** HTTPS expected in production (`config/admin.php` `secure => env('ADMIN_SECURE', true)`); cookie-based session via the `web` group. 🟢
- **CSRF:** not applicable — the only action is a `GET` with no state change. 🟢
- **Content type:** `text/html` (or a `3xx` redirect); there is **no JSON contract**. A modernized API exposing this data would need to define its own response schema (KPIs + chart series + top products) — out of scope for the legacy. 🔴
- **Idempotency / caching:** fully read-only and recomputed per request; no cache headers or precomputed store. Safe to cache at the reimplementation's discretion. 🟢 (`:34-119`)
- **Landing page:** `/dashboard` is **not** the app landing page — root `/` 301-redirects to `/pos`. 🟢 (`routes/web.php:22`)
