# Admin · Settings — Logic & Flows

Canonical = React demo at `/private/tmp/Navagoo_MI_dev/navagoo-app/src/portals/admin/Settings.tsx`.
Our app = Yii2 (`backend/controllers/SettingsController.php`, `backend/views/settings/*`).

## Headline finding

**These are two completely different surfaces under the same name.**

- **Demo "Settings"** is a 3-tab admin panel: (1) **Booking workflow** state-machine editor,
  (2) **Policies & documents** read-only table, (3) **Color system** live theming editor.
  It edits `GlobalConfig` (booking rules) + a runtime theme-token store. It is *pure operational/presentation config* — no contact info, no file uploads, no financial rates.
- **Our "Settings"** (`SettingsController`) is the legacy **"Website Settings"** record (single
  row id=1): policy-document *uploads*, financial rates (taxes, commission, deposit cap),
  system flags (distance range, unpaid-booking period, show-agents), and contact/social fields.

So the overlap is essentially **only "Policies & documents"**, and even there the demo is a
read-only listing while ours is an upload form. The two big demo features — the booking-workflow
editor and the color system — **have no equivalent anywhere in our codebase.**

---

## Demo tab 1 — Booking workflow editor (`BookingWorkflowCard`, Settings.tsx:53-180)

Reads/writes `GlobalConfig` via the Zustand store:
- `config.bookingTransitions: Record<BookingStatus, BookingStatus[]>` (Settings.tsx:54; type `types.ts:540`; seed `store/seed.ts:62-68`)
- `config.rescheduleStatuses: BookingStatus[]` (Settings.tsx:55; type `types.ts:544`; seed `store/seed.ts:71`)
- `config.noShowGraceMin: number` (Settings.tsx:56; seed `store/seed.ts:72`)
- mutated through `updateConfig(patch)` (Settings.tsx:57; store `store/store.ts:458-461`), which after every patch calls `reconcileBilling()`.

Flow:
1. Renders one row per FROM status (`STATUS_ORDER`, `lib/status.ts:38`). Each row shows the FROM
   badge, an arrow, and a toggle chip for every *other* status (Settings.tsx:99-138).
2. Clicking a chip toggles that transition in `bookingTransitions[from]` (`toggle`, Settings.tsx:59-63).
3. A separate per-row **Reschedule** pill toggles membership in `rescheduleStatuses` (`toggleReschedule`, Settings.tsx:67-71). This is a *capability* (time-move/reassign), independent of status moves.
4. **No-show grace** number input writes `noShowGraceMin` (clamped `>= 0`) (Settings.tsx:163-167).
5. **Reset to defaults** restores `DEFAULT_TRANSITIONS` / `DEFAULT_RESCHEDULE_STATUSES` / 15 min and toasts (Settings.tsx:73-80).

This config is **authoritative across the app**, not cosmetic:
- The status changer/guard rejects moves not in `bookingTransitions` (`allowedNextStatuses`, `lib/status.ts:60-65`; enforced `store/store.ts:518`).
- No-show can only be set after the grace delay (`canMarkNoShow`, `lib/status.ts:106-108`; enforced `store/store.ts:523`).
- Reschedule action + day-calendar drag are gated by `canRescheduleStatus` (`lib/status.ts:96-99`; enforced `store/store.ts:1381,1419,1620,1641`).

**Our computation:** none. We have no transition table, no grace setting, no reschedule allow-list.
A repo-wide grep for `bookingTransitions|rescheduleStatus|no_show_grace|allowedNext|canReschedule`
returns **zero** hits in `backend/ common/ frontend/ api/`. Booking-status changes in our app are
not gated by an admin-editable state machine.

## Demo tab 2 — Policies & documents (`PoliciesCard`, Settings.tsx:182-225)

Read-only `DataTable` of a hardcoded `DOCS` array (Settings.tsx:30-37): name, version, updated date,
published/draft badge, and a "View" button that fires a simulated toast (no real download). It is a
**listing**, not an editor — the demo does not upload or version documents.

**Our computation:** `backend/views/settings/_form.php:96-110` renders six file-upload fields
(terms+privacy × user/agent/shop). `SettingsController::actionIndex()` validates and stores each
upload to `@storage/web/source/profile/` and writes the `*_path` columns
(`SettingsController.php:104-146`), then fires `NotificationHelper` policy-update notifications to
customers/agents/shops (`SettingsController.php:151-159`). So ours is *more* capable for uploads but
has **no version / updated-date / status metadata** and no document listing table — there is no
concept of "v2.1 / published / draft" per document.

## Demo tab 3 — Color system (`ColorSystemTab`, Settings.tsx:262-330)

Live theme editor over ~26 color tokens (`COLOR_TOKENS`, `lib/theme.ts:50-90`) grouped into 8 groups
(`TOKEN_GROUPS`). Per token: a live preview badge/chip and a `ColorPicker` with change / save-default /
reset. Reads `themeColors` (overrides) + `themeDefaults` (saved baselines) from the store
(`types.ts:616,619`; actions `store/store.ts:463-477`). `dirtyCount` counts customized tokens
(Settings.tsx:280); "Reset all" clears every override/default (`resetThemeColors`, `store.ts:477`).
Colors are written to CSS custom properties at runtime so a change recolors every portal live, no rebuild.

**Our computation:** none. No theme-token store, no color picker, no runtime CSS-var theming surface.
Grep for `themeColor|theme_color|color_token|setThemeColor` returns **zero** hits on our side. Our
colors are fixed in the Tailwind "aurora" config (e.g. `#594279` brand, status hex literals).

## Demo top-level (`AdminSettings`, Settings.tsx:332-360)

`PageHeader` + a segmented tab switcher (workflow / docs / colors), client-side `useState` tab.
Our equivalent is a single scrolling form (`_form.php`) with three card sections (Policies, Financial,
System) — no tabs, and the section set does not match (we have Financial+System; demo has Workflow+Colors).

## Controller defects observed (ours)
- `actionUpdate($id)` is **dead** — it throws `NotFoundHttpException` unconditionally
  (`SettingsController.php:268-273`), yet `index.php:91` links every row's pencil to `['update','id'=>...]`.
  So the list's Update action 404s. The real edit happens at `actionIndex()` (renders `update.php`).
- Save logic calls `$model->save()` repeatedly inside the upload loop and again after
  (`SettingsController.php:138,149`), which is redundant but harmless.
