# Demo ⇄ Portal Gap Backlog (shop portal)

**Generated:** 2026-06-25 · **Method:** 11-agent fan-out audit (one per shop area), each comparing
the React demo source (`../Navagoo_MI/.../src/portals/shop/*`) + the parity spec
(`ai_specs/07_DEMO_PARITY/*`) against the current portal code, through a `frontend-design` +
`code-reviewer` lens. The P0 cluster was re-verified at code level.

> This is the answer to "study the whole thing again and see what we need to work on."

---

## Executive summary

The 5 parity waves worked: **every shop INDEX/LIST page is aurora and most are demo-faithful**
(dashboard, customers, services, promo, reviews scored "faithful"; packages, team, settings,
finance scored "partial" — structurally incomplete, not unskinned).

⚠️ **The `ai_specs/07_DEMO_PARITY/*` specs are STALE.** Multiple agents flagged them scoring
areas at 15–38% when the pages are now faithful (dashboard, customers, services especially).
Refresh them or stop trusting their scores.

The remaining work is **not a re-skin** — it falls into three buckets:

| Bucket | What | Where |
|---|---|---|
| **A. Broken wiring (P0 quick wins)** | Fully-built features that silently no-op or render raw JSON / full chrome | calendar drag, promo modal, block-time, detail Collect/Cancel |
| **B. Legacy / JSON-only modal forms** | The "open a JSON action endpoint or a whole page in an iframe" anti-pattern | booking Collect/Cancel/Reschedule, group booking, service form |
| **C. Whole missing screens (epics)** | Demo tabs/screens absent entirely | Team (Payroll, Structure, Compensation), Settings (5-tab + Commercials/Notifications/Walk-ins), Finance (Detailed Charges, Invoices, Settlement, Subscription) |

**Recurring bug class (root cause of most P0s):** aurora action buttons use `data-iframe-modal`
pointed at a **POST/JSON-only endpoint** (`/booking/collect`, `/booking/cancel`, `/booking/time-off`)
or a **full-chrome page** (`/booking/view`, `/agents-bookings/index`, promo create/edit with
`layout='tailwind'`). Opening these in an iframe shows raw JSON, mis-fires the action, or nests
the whole portal inside the modal. The fix pattern already exists: in-modal aurora sub-screens
(see `booking/_detail_modal.php` + the walk-in pay-now sub-screen) wired to the existing JSON
write paths, rendered via `iframe_aurora.php`.

---

> **STATUS 2026-06-25:** Sprints 1 & 2 are **DONE & verified in-browser** (pending commit).
> Remaining: Sprint 3 (group modal) + the epics — all need user sign-off because they touch
> DB schema / API (mobile app shares the API). See playbook §7.

## SPRINT 1 — P0 quick-win cluster  ✅ DONE (2026-06-25)

Tiny diffs that restore demo-core interactions currently dead/broken. **Do these first.**

1. **Calendar drag is dead.** `_day.php:158` `[data-cal-columns]` must emit `data-reschedule-url`,
   `data-reassign-url`, `data-date` (values already in the day payload — `BookingScheduleService:801-802`).
   JS (`booking-calendar.js:748-769`) is fully built and bails without them. *(S)*
2. **Promo create/edit shows full chrome.** `PromoCodeController.php:75,135` resets `layout='tailwind'`
   in actionCreate/actionUpdate → sidebar+navbar nested in the modal. Use `iframe_aurora` when
   `Yii::$app->request->get('iframe')`. Form is already aurora-styled. *(S)*
3. **Appointment block text order inverted.** `_booking_block.php:86` leads with the SERVICE name;
   the demo leads with the **customer first-name** (bold), then service, then time
   (`AppointmentBlock.tsx:102-113`). *(S)*
4. **Calendar polish trio** (all in `_day.php`/`index.php`): fullscreen button missing
   `data-label-enter/exit` (label never flips to "Exit"); gutter wrapper missing
   `[data-cal-gutter-body]` (height desyncs on zoom); **List dropped from the switcher** though
   `_list.php` + `?view=list` are fully built — demo switcher is List/Day/Month. *(S)*
5. **Dashboard recent-activity.** Table shows future-only scheduled/in-progress ASC; demo shows the
   **6 most-recent (incl. past) DESC** + a **Service column** + stacked When cell
   (`SiteController.php:643-648` vs `Dashboard.tsx:51-106`). Fix the garbled Arabic string in the
   same pass: `ar/backend.php:2469` "رسوم نavagoo" → "نافاجو". *(S)*
6. **Customers i18n + copy.** Add missing keys "Invite via WhatsApp" / "Invite via SMS" to both
   `en/frontend.php` + `ar/frontend.php` (currently English-only in AR). Optionally align
   classification badge copy to demo ("Navagoo customer"/"Shop customer"). *(S)*

---

## SPRINT 2 — Booking modal sub-screens  ✅ DONE (2026-06-25)

Rebuild the booking-detail secondary actions as **in-modal aurora sub-screens** in
`booking-calendar/_detail.php`, reusing existing JSON write paths — no new backend.

7. **Collect & complete** *(P0, M)* — `_detail.php:239` → `/booking/collect` (JSON-only). Build the
   demo `CollectPaymentBody` (cash/card + optional tip) sub-screen. `actionCollect` already exists.
8. **Cancel** *(P0, M)* — `_detail.php:251` → `/booking/cancel` (JSON-only). Build the demo
   `CancelModal` (customer/shop segmented, refund-zone badge, refund amount, reason). Reuse
   `actionCancel`/`actionCancelPreview`.
9. **Reschedule** *(P1, M)* — `_detail.php:248` → full-page `/booking/view`. Wire the existing aurora
   `booking/_reschedule_modal.php` (SlotPicker + conflict banner) instead.
10. **Block time** *(P0, M)* — `_day.php:113` → `/booking/time-off` (POST/JSON-only) renders blank.
    Render the existing `_timeoff_modal.php` via a GET action + `iframe_aurora` (it's only wired into
    the OLD `/booking/calendar` route today). Then restore the **time-off remove (X)** button +
    confirm wired to `removeTimeOffUrl` (`_specialist_column.php`). *(P1, M)*

---

## SPRINT 3 — Group booking  (P1, ≈ 2–3 days)

11. **New group** opens the entire `/agents-bookings/index` page in an iframe; demo opens a focused
    `NewGroupBookingModal`. Build the aurora group modal with the demo **SlotPicker** (Book now /
    Earliest / Next-available grid) + **ServicePickerModal** sub-screen instead of native date/time
    inputs + chips. Same for **GroupRescheduleModal**. *(L)*
    (Lower-priority follow-on: unify solo+party rows into one bookings table with the GroupExpanded
    drawer, per `Bookings.tsx:103-205`.)

---

## EPICS — whole missing screens (each its own multi-day effort; needs schema work)

### E1 · Team area — Compensation + Payroll + Structure *(XL)*
Demo `Team.tsx` is a 3-tab Segmented (Specialists | Payroll | Structure); portal has a card grid only.
- **Compensation section** in the add/edit modal *(P0)* — wage_type / pay_cycle / fixed_salary /
  commission_pct / commission_basis (+ new model columns). None exist today.
- **Payroll tab** *(P0)* — per-specialist completed count → serviced value → commission base
  (net-of-fees) → fixed + commission → total pay + tips owed, with grand totals.
- **Structure tab** *(P1)* — org chart (HierarchyTree, reportsTo reparenting). No `reports_to` field today.
- Plus: wage/salary/commission/title columns on the list, delete-guard modal (has-N-bookings →
  deactivate-instead), apply-hours-to-all, +1d per-shift badge, calendar-colour swatch, languages field.

### E2 · Settings area — unified 5-tab shell + missing tabs *(XL, needs schema)*
Demo `Settings.tsx` is ONE page, 5-tab Segmented (General / Scheduling / Payments / Commercials /
Notifications); portal is two routes (`/shop-settings` long form + `/payment-settings`).
- **5-tab Segmented shell** absorbing both existing forms *(P1)*.
- **Commercials tab** *(P1)* — read-only plan/status/billing/marketing-fee/processing-fee/VAT/min-withdrawal
  /settlement-hold panel (needs Shop columns or a billing source).
- **Payments: Walk-ins second toggle** per method (App vs Walk-ins) + footnote *(P1, needs walkin* columns)*.
- **General: email + bank + IBAN** fields *(P2 — columns already exist, pure view work, quickest win here)*.
- **Scheduling: 12h/24h time-format** Segmented *(P2, needs time_format column)*.
- **Notifications tab** (SMS/WhatsApp/Email/Push toggles) *(P2 — demo is local-only/illustrative)*.

### E3 · Finance area — 5-tab hub + missing tabs *(XL)*
Demo Finance is a 5-tab hub (`ShopFinanceLayout.tsx`: Earnings / Detailed Charges / Invoices /
Settlement / Subscription); portal has Earnings only. **Note: the per-booking detail engine
(`earnings/view.php`) is already RICHER than the demo drawer** (FormulaStrip, 5-type charge ledger,
refund-zone, P&L, carry-forward) — so the gap is hub structure, not depth.
- **Finance TabNav** *(P1)*.
- **Settlement tab** *(P1)* — FormulaStrip "net payout this cycle" + Eligibility table + transfer-requests
  Approve&send (reuse FinanceLedgerService + AgentsWallet eligibility math).
- **Detailed Charges tab** *(P1)* — shop-wide charge ledger w/ status + type filters (Charge model exists).
- **Invoices tab** *(P1)* — invoices table + View (InvoiceDocument) + Pay modal (Invoice model exists).
- **Subscription tab** *(P2)* — plan picker + payment cards.
- Polish: row-click drawer instead of separate view page; align earnings columns/KPI tiles to demo.

---

## P2 polish backlog (do opportunistically / batch later)

- **Services:** card shows agent COUNT not specialist NAMES; service form on legacy `iframe.php`
  (not `iframe_aurora`); missing variant picker, attached routines/add-ons pickers, bilingual
  description, image cropper; assigned-agents should be read-only ("managed in Team"); Packages
  should be the 5th catalogue tab not a separate route.
- **Packages:** unify `/package/index` + `/package/subscriptions` into counted tabs + Show-inactive;
  bundle card missing struck list price/savings; discount UI should be none/fixed/percent icon
  radios with live savings (subscription form already does this); bilingual name/desc.
- **Dashboard:** single-color donut ring; header copy ("Welcome back" + shop·date); "New walk-in booking"
  primary action → aurora walk-in flow.
- **Reviews/Rate:** **no demo counterpart** (reviews are only static badges on customer Discover cards) —
  out of scope for fidelity. Optional consistency cleanup only: aurora `_form.php`, swap native
  delete confirm → `ngConfirm`, fix `Yii::t('app',...)` categories.
- **Calendar:** "Inactive" vs "Off" specialist badge distinction (needs an inactive flag in payload).

---

## Housekeeping

- **Refresh `ai_specs/07_DEMO_PARITY/*`** — dashboard/customers/services/promo specs are stale
  (pre-Wave-2 scores). They under-report current state and mislead future audits.
- Keep this backlog + `HANDOVER.md` updated as sprints land.

## 2026-07-04 — Milestone-N (dynamic notifications v0.13.0) delta CLOSED

Full commit-by-commit disposition + phases: [`DEMO_SYNC_MILESTONE_N_PHASE2_PLAN.md`](DEMO_SYNC_MILESTONE_N_PHASE2_PLAN.md)
(status ✅ COMPLETE). The demo has no commits newer than `cb4712b` (v0.13.0) as of this date.

Remaining OPEN items from that milestone (deliberate follow-ups, not gaps):
- ~~`settlement_received` shop alert~~ **CLOSED 2026-07-05** (`fe846bb`) — `dispatchShopEvent()`
  transfer-scoped entry; fires on the transition into SETTLED in backend WithdrawalController.
  NOTE: inert until an admin authors + approves the in-app template for the trigger
  (tokens: `[shopName]`, `[amount]`, `[settlementDate]`, `[transferNum]`).
- ~~Group-booking create/collect bypass notification call sites~~ **CLOSED 2026-07-05** (`fe846bb`) —
  group create/cancel/collect/complete now fire the same per-child events as the solo flows.
- REAL SMS/WhatsApp delivery + DLR (Msegat poll / T2 push webhooks) per the demo integration
  handoff spec (demo docs commits `c79f6b7`/`2ba6594`/`62a31db`); `SMSHelper`/`WhatsAppHelper`
  are ready but intentionally unwired — delivery today is in-app + ledger only.
- Customer-audience in-app rows are still unaddressed (`to_id = null`) — routing them into the
  mobile customer feed needs sign-off (risk: doubles the legacy NotificationHelper pushes).

**2026-07-05 engine fixes** (`7d2a2e0`, found while closing the follow-ups): paid-channel
idempotency keyed on shop_id suppressed every paid send after a trigger's first (now keyed per
booking; usage counters re-scoped via JOIN booking); shop-audience in-app rows had `to_id = null`
and never surfaced in the to_id-scoped bell/feed (now addressed to the shop owner); the Settings
"in-app this month" tile filtered by a never-written `from_id` (always zero).
