# Admin · Dashboard — Parity matrix

Demo (canonical): `portals/admin/Dashboard.tsx` + `store/selectors.ts`
Ours: `backend/controllers/SiteController.php` + `backend/views/site/index.php`

| Demo behavior | Demo ref | Our ref (file:line or —) | Status | Note |
|---|---|---|---|---|
| Admin dashboard route/page exists | Dashboard.tsx:25 | SiteController.php:60 | done | Tailwind reskin in views/site/index.php |
| Role/auth gate to reach dashboard | (store, none) | SiteController.php:69,129 | done | manager/admin/shopOwner; else logout |
| Platform-wide aggregation (all shops) | selectors.ts:428 | SiteController.php:89 | done | admin sees all; `?shop_id=` drills down |
| KPI: Total Active Shops (+ N cities) | Dashboard.tsx:101 | SiteController.php:859 (view:294) | partial | active count exists in Shop-Stats card; no city count; not a headline KPI |
| KPI: Today's Bookings (+ total) | Dashboard.tsx:108 | SiteController.php:443 | partial | we count per range (default today); no "N total" caption |
| KPI: Platform GMV | Dashboard.tsx:115; selectors.ts:432 | — | missing | no booking-value/GMV metric or model |
| KPI: Navagoo Earnings | Dashboard.tsx:123; selectors.ts:429 | — | missing | no Charge model |
| KPI: Pending Transfer Requests (+total) | Dashboard.tsx:131; selectors.ts:435 | — | missing | no TransferRequest model |
| KPI: Open Refund Cases | Dashboard.tsx:140; selectors.ts:449 | — | missing | no refund metric |
| KPI delta/trend pill | Dashboard.tsx:118 | SiteController.php:543; view:264 | partial | ours = period-over-period on booking counts; demo deltas are static finance % |
| Recent bookings (last 6 by date, any status) | Dashboard.tsx:29 | SiteController.php:555 (view:320) | partial | ours shows next-5 *upcoming* SCHEDULED/INPROGRESS instead of recent history |
| Recent table cols: id, Shop avatar, Customer, Service, Value, Status badge | Dashboard.tsx:38 | view:337 | partial | ours: Shop, Customer, time, Duration, Specialist; no id/Service/Value/Status/avatar |
| Row click → booking detail; "View all" | Dashboard.tsx:163,150 | — | missing | no row navigation / view-all link |
| Settlement queue rail (requested count, total pending, oldest) | Dashboard.tsx:178 | — | missing | no finance/settlement feature |
| "Process settlements" CTA → /admin/finance | Dashboard.tsx:88 | — | missing | no finance route |
| Subscription watch (every shop, status label) | Dashboard.tsx:213; selectors.ts:41 | — | missing | no Subscription model/table |
| Subscription warn flag (no sub/free_period/flagged) | Dashboard.tsx:223 | — | missing | — |
| Money formatting (money/moneyCompact) | lib/format | — | missing | no money on our dashboard |
| Date-range filter (today/7d/30d/custom) | (none) | SiteController.php:371; view:224 | done (ours only) | NEW vs demo |
| Specialist hours / occupancy charts | (none) | SiteController.php:628; view:417,428 | done (ours only) | NEW vs demo |
| Booking-status doughnut | (none) | SiteController.php:692; view:404 | done (ours only) | NEW vs demo |
| New/returning customer cohort charts | (none) | SiteController.php:759; view:440 | done (ours only) | NEW vs demo |
| Store rating widget + latest reviews | (none) | SiteController.php:714; view:371 | done (ours only) | NEW vs demo |
| Shop-owner reuses dashboard (role-scoped) | (separate portal) | SiteController.php:79 | done (ours only) | merged admin+owner |
| Empty / no-data states | (Table empty) | view:353,385,410 | done | covered for tables + charts |
| RTL support | n/a (LTR demo) | layouts/tailwind.php + logical props | done | ours bilingual ar/en |

## Assessment

The two dashboards target **different jobs**: demo = platform-finance/settlement console;
ours = booking-operations analytics (and doubles as the shop-owner dashboard). The entire
finance half of the demo (GMV, earnings, settlement queue, transfers, refunds, subscription
watch) has **no model or UI on our side** — these are the largest, most central demo widgets.
Conversely we ship substantial analytics the demo lacks. Counting against the canonical demo's
intended behavior, most headline demo widgets are missing or only partially mapped.

Area score: 28%

## Verified verdict (adversarial)

Re-read OUR code against the demo. Confirmations and downgrades below (file:line).

| Demo behavior | Analyst | Verified | Evidence |
|---|---|---|---|
| Admin route/page + role gate (manager/admin/shopOwner) | done | done | `SiteController.php:69` gate; guest/owner-without-shop → logout/redirect (`:81,129`). Demo has no in-file role guard; functional match. |
| Platform-wide aggregation (all shops) + `?shop_id` drilldown | done | **partial** | Booking stats aggregate all shops (`:387` no shop filter) — OK. BUT drilldown `<select>` only lists `STATUS_ACTIVE` shops (`:104`), so admin cannot drill into NEW/inactive shops. AND `getShopStatistics()` (`:840-866`) calls `->where([...status...])` on clones of a base query that already set `->where(['id'=>shop])`; the status `where` **overwrites** the id filter, so Active/New/Inactive tiles ignore the selected shop. Real scoping defect. |
| KPI: Total Active Shops (+N cities) | partial | partial | Active count is a Shop-Statistics tile (`getShopStatistics :846`, view `:294`); no distinct-city metric; not a headline KPI. Confirmed. |
| KPI: Today's Bookings (+total) | partial | **partial (weak)** | There is **no "Today's Bookings" headline KPI**. Our 4 KPI cards are status splits — Scheduled/Completed/In-progress/Cancelled (`:464-501`). Default range is `today` so the scheduled card ≈ today's scheduled, but no single "today's bookings" value and no "N total in system" caption. Also demo counts `appointmentDate`; ours filters `booking_date`. |
| KPI: Platform GMV | missing | missing | No booking-value/GMV anywhere. Confirmed (`selectors.ts:432`). |
| KPI: Navagoo Earnings | missing | missing | No Charge model. Confirmed (`selectors.ts:429`). |
| KPI: Pending Transfer Requests (+total payout) | missing | missing | No TransferRequest model. Confirmed (`selectors.ts:435`). |
| KPI: Open Refund Cases | missing | missing | No refund metric. Confirmed (`selectors.ts:449`). |
| KPI delta/trend pill | partial | partial | Ours = period-over-period on booking counts (`calculateTrend :543`, view `:264`); demo deltas are static finance %. Confirmed. |
| Recent bookings feed (last 6 by date, any status; id/avatar/Service/Value/Status; row→detail; View all) | partial | partial | Ours = `getUpcomingBookings` (`:555`): next-5 with `booking_date >= today` AND status IN (SCHEDULED, INPROGRESS) — i.e. upcoming, not recent history. Cols = #/Shop/Customer/time/Duration/Specialist (view `:337`). Missing id/Service/Value/Status badge/avatar; no row navigation; no "View all". Confirmed partial. |
| Settlement queue rail + CTA → /admin/finance | missing | missing | No finance/settlement feature. Confirmed. |
| Subscription watch rail | missing | missing | No Subscription model. Confirmed. |
| Money formatting | missing | missing | No money values on our dashboard. Confirmed. |
| Empty / no-data states | done | done | Verified: upcoming table empty row (view `:353`), reviews empty `:385`, rating "—" dash `:373`, status/occupancy/cohort charts each guard `$has*Data` (`:410,417,440`). Genuine. |
| Date-range filter today/7d/30d/custom (max 30d) | done (ours only) | done (ours only) | `resolveDashboardRange :371`; custom clamps to 30 days (`:407`) and falls back to today on invalid input (`:411`). NEW vs demo — no demo parity credit. |
| Specialist hours/occupancy, status doughnut, cohort, rating widget | done (ours only) | done (ours only) | All present and data-guarded. NEW vs demo — no demo parity credit. |
| Shop-owner reuses dashboard, role-scoped | done (ours only) | done (ours only) | Owner branch scopes every query by `shop_id` (`:80,393,569,635`), hides shop column/dropdown. NEW vs demo (demo keeps owner portal separate). |
| RTL/bilingual | done | done | All strings via `Yii::t`, logical props in Tailwind layout. |

### Net adjustment
Two analyst "done" rows downgraded to **partial**:
- Platform-wide aggregation + drilldown — drilldown excludes non-active shops and the per-shop Shop-Statistics tiles are not actually scoped to the selected shop (clone `where()` overwrites the id filter). Behavioral defect, not a clean match.
- (Today's Bookings already partial; tightened — effectively no dedicated KPI.)

The entire finance half of the canonical demo (GMV, earnings, transfers, refunds, settlement queue, subscription watch, money formatting — the largest widgets) remains **missing** with no backing models. Our NEW analytics earn no demo-parity credit. With the aggregation downgrade and the scoping defect on top of the prior assessment:

**Adjusted area score: 25%** (was 28%)
