# Admin BI — Wave 8 finish + hardening plan

**Date:** 2026-07-07 · **Owner:** next execution pass · **Context:** the net‑new admin
financial‑BI cluster (Costs → P&L → Dashboard‑BI → Home) + Events + Users&Roles +
Offers/Payment‑methods + turquoise chrome are **built and browser‑verified**
(see `ADMIN_PORTAL_PARITY_AUDIT_2026_07_07.md`). This plan covers what remains: the two
deferred Wave‑8 items, plus the hardening the build now deserves before it's called done.

**Legend:** P0 = finish the promised scope · P1 = protect the work · P2 = correctness/polish ·
P3 = housekeeping. Effort in ideal focused time. Every UI step ends with a **browser + `node --check`** pass (the Dashboard bug proved HTML‑only checks are insufficient).

---

## Phase A — Finish Wave 8  (P0)

### A1 · Nav section headers (8c)  · ~30 min · low‑med risk
Group the admin sidebar into the demo's four sections so the IA matches.
- Teach `backend/views/layouts/_tw_admin_sidebar.php` to render a **section‑label** node
  (a node with `header => true` / no `url`) as an uppercase muted divider.
- In `backend/views/layouts/menu/Menu.php` insert section markers so the order reads:
  **Home · Dashboard** · **Operations** (Shops/Bookings/People) · **Money** (Finance · P&L ·
  Costs · Subscription Plans/Offers/Subscriptions/Payment) · **Growth** (Marketing · Support &
  content) · **System** (Events · Users & Roles · Settings).
- Keep every existing `visible`/`active` expression untouched — only wrap/insert, never
  re‑key. Collapse a section to nothing when all its children are permission‑hidden.
- Verify: login both as `admin` (all sections) and a `manager` (subset) — no empty headers.

### A2 · Off‑CDN built CSS + self‑hosted Lucide (8b)  · ~2–3 h · med risk (mitigated by visual QA)
Today `backend/views/layouts/tailwind.php` pulls **`cdn.tailwindcss.com`** (Play CDN, dev‑only)
and **`unpkg.com/lucide@latest`** (unpinned). Move both in‑house — the layout header itself
says *"PROD: swap the CDN."*
1. **Tailwind source** `backend/web/css/tailwind.src.css` — `@tailwind base/components/utilities`
   + the theme currently inlined in `tailwind.php` (brand/accent/ink/st/**cyanbrand**,
   fonts, radii, shadows, `bg-side/head-admin` gradients incl. the RTL head variant).
2. **Config** — extend `tailwind.config.js` (or a sibling `tailwind.admin.config.js`) with
   admin `content` globs: `backend/views/**/*.php`, `backend/web/js/**/*.js`,
   `frontend/components/**/*.php` (shared `Aurora::TONES`). **Safelist** the dynamic hue
   classes that are built in PHP arrays (status/surface/role tones, `st-*`, chart hexes are
   inline styles so they're fine) so the purge can't drop them.
3. **Build script** — `npm run build:css:admin` → minified `backend/web/css/tailwind.css`.
4. **Lucide** — vendor `lucide.min.js` to `backend/web/js/libs/` (pin the version), replace
   the unpkg `<script>`.
5. **Swap** the two CDN tags in `tailwind.php` for `<link rel="stylesheet" href="/css/tailwind.css">`
   + the local Lucide `<script>`; drop the inline `tailwind.config`.
6. **Verify hard** — screenshot ALL 8 admin surfaces + the legacy AdminLTE‑free pages after
   the swap; diff against the current turquoise screenshots. Any missing class → add to the
   content glob/safelist, rebuild. Add `build:css:admin` next to the shop `build:css` in the
   CLAUDE.md build note.
- **Risk note:** the only real failure mode is a purged dynamic class → visible style drop.
  The screenshot diff catches it; nothing ships un‑eyeballed.

---

## Phase B — Protect the work  (P1)

### B1 · Backend render‑smoke + inline‑JS check  · ~1 h · low risk
The frontend has `tests/smoke/render-smoke.sh`; the backend has none — which is why the
Dashboard JS bug slipped through. Add `tests/smoke/render-smoke-admin.sh`:
- Log in via the backend `SignIn` form (admin creds from env, mirroring the frontend smoke).
- GET the 8 new routes (+ a few legacy ones) → assert 200 + no PHP error signature.
- For each, extract every inline `<script>`, strip `<?= ?>`, and `node --check` it →
  fail on any JS syntax error. **This single step would have caught the Dashboard bug.**
- Wire it into whatever runs the frontend smoke.

### B2 · Unit tests for the money/BI math  · ~2 h · low risk, high value
The finance aggregation is the highest‑stakes new code. Codeception unit tests
(`common/tests/unit/`) for:
- `PlatformPnlService`: `revenueByLine` (marketing←shop_earning + charge lines, ex‑VAT,
  reversed/pending excluded), `costForPeriod` for all 8 bases (incl. `per_online_txn` pct+fixed),
  `pnl` margin + `delta`, `periodRange`/`comparePeriod`/`addMonths` month‑end clamp.
- `PlatformBiService`: `gmv` (completed only), `noShowRate`, `flags` counts.
- `SubscriptionMetricsService`: `monthlyEquivalent` per period (monthly/six/twelve), `atRisk`.
Seed a tiny fixture (a couple shops, charges, bookings, one past‑due sub) and assert exact numbers.

---

## Phase C — Correctness & polish  (P2)

### C1 · AR / RTL visual pass  · ~45 min
The verify pass ran as an EN admin. Switch language to Arabic and re‑screenshot the 8 pages:
confirm the turquoise header gradient flips (RTL variant), tables/badges mirror, charts and
the segmented period control read correctly RTL, and no clipped Arabic. Fix any RTL‑only breaks.

### C2 · Validate the revenue mapping assumptions  · ~1 h
Document + sanity‑check the two modelling choices so Finance trusts the P&L:
- **marketing revenue ← `shop_earning.navagoo_marketing_fees`** (not a `charge` row in our
  ledger). Reconcile the P&L "Marketing fees" line against the existing Navagoo‑earnings figure
  for the same period; note any delta.
- **`per_online_txn`** counts booking collections where `payment_mode ∈ {online,deposit}` +
  subscription charges. Confirm that's the intended Paymob‑cost basis, or refine.
- Fold the conclusion into the audit doc's §2b.

### C3 · Carry‑over Finance‑review items (from the earlier pass)  · ~1 h
Decide/fix the 3 flagged items: Settlement "Charges SAR 0.00 / Net −6.00" not reconciling for
cash walk‑ins (marketing fee not surfaced in Charges until settlement); walk‑in booking labelled
"100% Online"; marketing fee charged on a walk‑in. Confirm against the demo, then fix or document
as intended.

---

## Phase D — Housekeeping  (P3)

### D1 · Commit in logical chunks  · ~20 min
Nothing is committed yet. On the current branch, commit in reviewable slices:
`feat(admin): navagoo_cost migration + model` → `feat(admin): platform P&L/BI/subscription
services` → `feat(admin): Costs/Analytics/Dashboard controllers+views` → `feat(admin): Events +
Users&Roles + Offers/Payment tabs` → `feat(admin): turquoise chrome + menu + i18n` →
`chore(admin): off‑CDN build + smoke + tests`. (Commit only when the user asks.)

### D2 · Local‑dev admin password  · trivial
`admin` / `NavAdmin!2026` was set for verification (local DB only). Leave it (user has it) or
rotate on request; note it's dev‑only and not for any other environment.

---

## Suggested order & rough total

1. **A1** nav sections (quick win, closes the visible gap) → **B1** backend smoke (cheap safety
   net, reusable for the rest) → **A2** off‑CDN (the big one, now guarded by the smoke) →
   **B2** unit tests → **C1** RTL → **C2/C3** correctness → **D** commit.
2. **Rough total:** ~1.5 days focused. P0+P1 alone (the "done‑done" line) ≈ ~5–6 h.

Everything is backend‑only; **nothing touches the shared `api/` tier**, so no mobile‑app risk.
