# Shop · Settings — Logic & Flows

Demo (canonical): `/private/tmp/Navagoo_MI_dev/navagoo-app/src/portals/shop/Settings.tsx`
Data shape: `src/types.ts` (Shop interface, L16–70), `GlobalConfig` (L520–534).
Store action: `src/store/store.ts:1146` (`updateShop(shopId, patch)` — shallow merge).
Selectors: `src/store/selectors.ts:40` (`planById`), `:41` (`subscriptionForShop`).

Our side:
- Profile/hours/location: `frontend/controllers/ShopSettingsController.php:117` (`actionIndex`) + `frontend/views/shop-settings/index.php`.
- Payment methods + deposit: `frontend/controllers/PaymentSettingsController.php:43` + `common/models/ShopPaymentSettings.php` + `frontend/views/payment-settings/index.php`.

## Demo: one page, five tabs (Segmented)

`Settings.tsx:23` holds a local `view` state switching between
`general | scheduling | payments | commercials | notifications`. All tabs read the
single active `shop` object (`Settings.tsx:21`, `state.shops.find(activeShopId)`).
Edits call `updateShop(shop.id, patch)` directly — there is **no Save round-trip** for
most fields (instant store mutation); only the General tab has an explicit Save button
that batches `{commercialName, email, ownerMobile}` then toasts (`Settings.tsx:73–80`).

### 1. General (`Settings.tsx:50–86`)
Editable: commercialName, email, ownerMobile. Read-only/disabled: bankName, iban.
Save button batches the three editable fields + `toast.success('Settings saved')`.

### 2. Scheduling (`Settings.tsx:89–150`)
- Opens / Closes = `<input type="time">` bound to `shop.openTime` / `shop.closeTime`,
  each writing immediately via `updateShop`.
- **Overnight hint**: `hhmmToMin(closeTime) <= hhmmToMin(openTime)` ⇒ shows
  "Closes next day (+1d)" hint on the Opens field (`Settings.tsx:96–99`).
  Logic lives in `lib/schedule.ts:48` (`hhmmToMin`) / `:93` (`isOvernight`).
- Calendar slot size = Select over [5,10,15,30]; **disabled when `shop.slotStepLocked`**
  (admin policy lock) (`Settings.tsx:118`).
- Time format = Segmented 12h/24h (`shop.timeFormat`, default '12h') (`Settings.tsx:131`).

### 3. Payments (`Settings.tsx:153–212`)
Three methods, each with **two toggles**: an **App** toggle (`acceptOnline/acceptDeposit/
acceptOnVisit`) and a **Walk-ins** toggle (`walkinOnline/walkinDeposit/walkinOnVisit`).
- Deposit method shows desc `${depositPct}% upfront`.
- When `acceptDeposit` is on, a `Deposit %` NumberInput appears, **clamped on input** to
  `[0, shop.maxDepositPct ?? config.maxDepositPct]` (`Settings.tsx:198–205`).
- Effective cap = per-shop `maxDepositPct` override, else global `config.maxDepositPct`.

### 4. Commercials (`Settings.tsx:215–251`) — READ-ONLY
DataRows derived from selectors + shop:
- Subscription plan = `planById(subscriptionForShop(shop).planId).name` as a Badge.
- Status = sub.status (active→teal, free_period→amber, else slate), `replace('_',' ')`.
- Next billing = `date(sub.nextBillingDate)`.
- Marketing fee rate = `pct(shop.marketingFeeRatePct)`.
- Payment processing fee = `pct(shop.paymentProcessingFeeRatePct) + money(fixed)`.
- VAT registered = Yes/No (`shop.vatRegistered`).
- Min withdrawal = `money(shop.minWithdrawalAmount)`.
- Settlement hold = `${shop.settlementHoldDays} days`.
Footnote: "To change a rate, contact Navagoo."

### 5. Notifications (`Settings.tsx:254–300`)
Four channels (SMS, WhatsApp, Email, Push) as toggles. **Local state only**
(`notif` useState, `Settings.tsx:30`) — not persisted to the shop/store in the demo.
SMS/WA descs show per-message fee from `config.smsUnitFee` / `config.waUnitFee`.

## Our implementation vs demo logic

| Demo concern | Our equivalent | Notes |
|---|---|---|
| General profile (name/email/mobile) | `ShopSettingsController::actionIndex` saves whole `Shop` via `$shop->load($_POST)&&$shop->save()` (L126). View binds `title`, `mobile`, `gender` | We have NO email field on shop settings; commercialName≈`title`; ownerMobile≈`mobile`. We add many fields the demo lacks here (media, location/map, refund rules, no-show, website, about/cancel terms). |
| bankName / iban (read-only) | — | Not present anywhere in Shop model/view. **Missing.** |
| openTime/closeTime | `open_at`/`close_at` via kartik TimePicker (view L227/L231) | Present. Overnight hint replicated server+JS (`isOvernight()`, view L234–249, L713). Good parity. |
| slotStepMin + lock | `slot_time_step` dropdown `getSlotSizeOptions()` (view L323), locked via `isSlotTimeStepLocked()` + role check (view L318) | Present; lock logic richer (admin/manager bypass). |
| timeFormat 12h/24h | — | **Missing.** No per-shop time-format toggle. |
| Payments: App accept toggles | `ShopPaymentSettings.pay_online/pay_deposit/pay_on_visit_enabled` | Present. |
| Payments: **Walk-in** per-method toggles | — | **Missing.** Our model has no walkin* columns; single set of toggles only. |
| depositPct clamp to effective max | `ShopPaymentSettings::validateDepositPercentage` + `getEffectiveMaxDeposit()` (model L83/L112) | Present, server-side validated. Effective cap = `shop.max_deposit_percent_override` else `Settings.max_deposit_percent` else 100. Equivalent to demo's per-shop-else-global. |
| Commercials read-only panel | — | **Missing entirely.** No subscription plan, status, billing date, marketing/processing fee, VAT, min withdrawal, settlement hold surfaced on shop settings. |
| Notifications channel toggles | — | **Missing.** (Sidebar links to `/notifications` list — a different feature, not a channel-preferences card.) |

## Persistence model difference
- Demo: most fields write instantly to the Zustand store (`updateShop`); General + everything
  is in-memory only (no backend). Notifications never persist.
- Ours: classic POST→validate→save→flash→refresh on both controllers; real DB persistence and
  server-side validation. This is a strict improvement on durability but the **field coverage
  is narrower** (no commercials/VAT/notifications/walk-in/time-format/bank).
