# Admin · Geography — Logic & Flows

Demo (canonical): `/private/tmp/Navagoo_MI_dev/navagoo-app/src/portals/admin/Geography.tsx`
Our impl: `backend/controllers/{City,District,Government}Controller.php`, `common/models/{City,District,Government}.php`, `backend/views/{city,district,government}/*`

## Demo data shape
- `City` (types.ts:559) = `{ id, name, districts: string[], active: boolean }`.
  Districts are **plain strings embedded in the city** — there is no separate District entity.
- Seed (`store/seed.ts:418-436`): 3 cities (Riyadh active, Jeddah active, Dammam inactive), each with an inline `districts` array. No "government/region" tier exists in the demo at all.

## Demo flows (Geography.tsx)
1. **Read all cities** — `useStore(s => s.cities)` seeds local `useState` copy (`Geography.tsx:12-13`). Renders a responsive card grid (1 / 2 / 3 cols).
2. **Per-city shop count** — computed live: `shops.filter(s => s.city === c.name).length` (line 26). Match is by **city name string**, not id. Pluralized "shop/shops" (line 36).
3. **District chips** — `c.districts.map(...)` rendered as slate `Badge`s (lines 49-53). Count shown as `{c.districts.length} districts`.
4. **Toggle active** — `Toggle` per card flips `c.active` in local state via `toggle(id)` (lines 14-15, 39-46) and fires a `toast.info("<city> enabled/disabled")`. **Local-only / optimistic** — no persistence, no store action, no API. State resets on remount.

That is the entire feature: list + per-city active toggle + derived counts. No create/edit/delete, no district CRUD, no government tier, no search/pagination.

## Our implementation
Three separate Gii-generated CRUD controllers, each with index/view/create/update/delete:
- `GovernmentController.php` — extra "government/region" tier the demo does not have. Guards guests (`:17-23`), permission `checkPermmissions('government')`. Has tabular `addCustomer` action (`:203`).
- `CityController.php` — full CRUD, `loadAll/saveAll` via mootensai RelationTrait, `CitySearch` GET filter by name (`backend/views/city/index.php:50`).
- `DistrictController.php` — full CRUD; District is a **first-class table** (`district.city_id` FK, plus `slug, meta_description, direction, region`, base model `common/models/base/District.php:13-22`). Filterable by name + city (`backend/views/district/index.php:53-72`).

### How the demo's logic maps onto ours
- **Active toggle**: NOT computable. Neither `city` nor `district` table has an `active`/`status`/`enabled` column (confirmed grep on both base models — only doc-comment "active query" hits). No analog exists; would require a migration.
- **Per-city shop count**: derivable via `Shop.city` FK (`common/models/base/Shop.php:32,468` — `hasOne(City, ['id'=>'city'])`), but **not surfaced** in any city view. The city index shows only serial #, name, edit/view (`backend/views/city/index.php:71-104`).
- **Districts-per-city count / chips**: derivable (`district.city_id`) but **not surfaced** on the city list. Our model is normalized (separate District rows) vs demo's denormalized inline string array — data lives in different shapes.
- **City matching by name**: demo joins shop↔city on `name`; ours joins on integer FK `shop.city = city.id`. Ours is more correct, but the demo's derived-count UI is absent.

## Net
Our CRUD is far more capable (real persistence, search, pagination, normalized districts, extra government tier) but the **specific UX the demo encodes — a card grid with live shop/district counts and an active toggle — is missing**, and the underlying `active` flag does not exist in our schema.
