# Shop · Services catalogue — UI / Screens / States

Canonical: `portals/shop/Services.tsx`. Ours: `frontend/views/shop-service/{index,_form,view}.php`.

## Screens — demo

1. **Catalogue page** (`Services.tsx:111-167`)
   - `PageHeader` with shop commercial name subtitle + primary "Add …" button whose label changes
     per tab (`Services.tsx:103-109`).
   - `Segmented` 5-tab switch with live counts + a `showInactive` toggle (`:124-138`).
   - One responsive card grid per tab (`grid-cols-1 sm:grid-cols-2 lg:grid-cols-3`).
   - `EmptyState` per tab with kind-specific icon + copy (`:298, 370, 431, 485`).

2. **Service card** (`ServicesGrid`, `:291-362`): square thumb, category badge + status badges
   (Inactive / Hidden), 4-action `LifecycleActions` cluster, name, duration with "+N" freebie
   minutes, struck-through base price + final price, per-item VAT line (only if VAT-registered),
   specialist names row.

3. **Bundle card** (`:364-423`): "Bundle" badge, member-service names joined with " + ", free
   attached add-on chips with a FREE badge, struck list price + bundle price.

4. **Package card** (`:425-475`): sessions badge, auto-renew badge, validity-days + price-per-
   session line, price.

5. **Freebie card** (routines/add-ons, `:479-527`): FREE badge, duration only (no price).

6. **CatalogueModal** (`:665-1157`) — `size="xl"`: identity (bilingual name + desc, image picker
   w/ cropper + in-app circle/rounded preview, **variant picker** w/ per-variant bilingual name),
   3-card pricing (before / discount icon-toggle / after with live savings), details (duration+
   category optgroups for services; sessions+validity+auto-renew+billing-period for packages;
   included-services multi-picker for bundles/packages), routines-before / routines-after /
   free-add-ons multi-pickers, read-only assigned-agents section, and a "what's missing"
   validation banner gating the Save button.

7. **FreebieModal** (`:529-654`) — `size="lg"`: bilingual name+desc, duration, image, active +
   visible toggles.

## Screens — ours

1. **Index** (`index.php`): page header + create button; a **Service Categories cover-image
   customiser** card (collapsible, per-`ServiceCategoryAssignment`, with live preview / show-more,
   `:67-186`) — **no demo equivalent**; a single service card grid (`:188-262`); real `LinkPager`
   pagination (`:265-275`); empty state (`:277-290`).
2. **Service card** (`:201-260`): gradient/glyph or image thumb, name, linked-Service name as a
   pill, price chip, duration chip, agent **count**, edit + delete actions only.
3. **`_form.php`** (full page): image upload (filekit) + gallery manager, bilingual name tabs,
   3-panel pricing with AJAX VAT calc, duration dropdown, category dropdown, agent Select2.
4. **`view.php`**, **`_pdf.php`** — detail/print surfaces (no demo analog).

## Concrete UI gaps (ours vs demo)

| Demo UI | Ours | Gap |
|---|---|---|
| 5-tab segmented catalogue | Services grid only | **Missing** routines/add-ons/bundles tabs; packages on a separate page |
| `showInactive` toggle + Inactive/Hidden badges | none | **Missing** — no inactive/hidden concept |
| Lifecycle cluster (visibility + active toggle + edit + delete, with confirms/toasts) | edit + delete only | **Missing** visibility & active toggles |
| Modal create/edit | full-page form | **Partial** (renderAjax path exists) — different UX |
| Variant picker (images + bilingual names) | none | **Missing** |
| Routines-before / after / free add-ons pickers | none | **Missing** |
| Struck base price + final + savings feedback | price chip only (computed server-side) | **Partial** — no strike-through / "saves X%" / "zeroes" warning on the card |
| Per-item VAT line on card | none on card | **Missing** on card (present in form) |
| Specialist **names** on card; agents read-only in form | agent **count** on card; agents editable in form | **Divergent** |
| Image cropper + in-app circle/rounded preview | filekit upload (server fit 215×215) | **Partial** — no client crop/preview |
| Bilingual description field | name bilingual only (no description field found in form) | **Missing** description |
| Empty state per tab | one services empty state | **Partial** |
| Category cover customiser | — | **Extra in ours** (not in demo) |
| RTL: demo uses logical props (`ms-`, `pe-`, `end-`) throughout | Tailwind logical props used in our views too | OK |
