# Phase 6: Navigation Manager UI

## Goal
Build the centralized visual control panel (the ðŸ§­ Navigation Manager) inside `/page_editor_admin` so the admin can manage all menu locations from one single screen.

## Important Context
> [!CAUTION]
> **DO NOT modify the Neon database schema.** The `navigation_items` table is already created. You only need to build the UI and the API routes that read from and write to it.

> [!NOTE]
> The Navigation Manager is built as a **modal** (a pop-up window) inside the existing `/page_editor_admin` interface. It is NOT a separate page. It is triggered by clicking a ðŸ§­ icon button in the editor's top header.

---

## Requirements

### 1. The API Data Layer (Read & Write)

These two functions already exist in `lib/navigation.ts`:

```ts
// For the LIVE FRONTEND â€” returns only visible items (is_active = true)
getNavigationItems(menuLocation)

// For the ADMIN Navigation Manager â€” returns ALL items including hidden ones
getAllNavigationItems()
```

> [!WARNING]
> **CRITICAL:** The Navigation Manager admin UI must use `getAllNavigationItems()` â€” which fetches ALL items for the site across all locations. If you use `getNavigationItems()`, hidden items disappear from the manager forever and cannot be toggled back on.

The `buildMenuTree()` helper in `lib/navigation.ts` converts the flat list from DB into a nested tree using `parent_id`. Use it both in the admin manager and on the live frontend.

**API Routes:**
- `GET /api/navigation-items` â€” returns flat list. Add `?all=1` param to return all items (including inactive) for admin use.
- `POST /api/navigation-items` â€” creates a single item.
- `POST /api/navigation-items/bulk-save` â€” bulk upsert/delete (used by Navigation Manager on "Save Changes").

---

### 2. The Modal UI Layout

The modal sections are controlled by two **hardcoded** constants inside `NavigationManagerModal.tsx`:

```typescript
const LOCATION_CONFIG: Record<string, { label: string; emoji: string; color: string }> = {
  top_header: { label: 'Top Header', emoji: 'ðŸ“Œ', color: '#1e40af' },
  footer:     { label: 'Footer',     emoji: 'ðŸ“„', color: '#374151' },
};
const LOCATION_ORDER = ['top_header', 'footer'];
```

> [!IMPORTANT]
> **`category_bar` is optional and site-specific.** If a new website has a Blue Category Bar driven by `navigation_items`, add `category_bar` to both `LOCATION_CONFIG` and `LOCATION_ORDER`. If the category bar is driven by a separate `categories` table (e.g., synced from Ranktraction), do NOT add it â€” the categories will be shown as locked rows in the Top Header instead (see Section 7).

Visual layout of the modal:
```
ðŸ§­ Navigation Manager
â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
ðŸ“Œ Top Header                     [6 top-level items]
  ðŸ”’ ã‚¢ã‚¯ãƒ†ã‚£ãƒ“ãƒ†ã‚£  /category/...  [CAT]   â†� locked, from categories table
  ðŸ”’ ã‚°ãƒ«ãƒ¡          /category/...  [CAT]   â†� locked
  âœ… test1           /test1         [PAGE]  âœ�ï¸� ðŸ—‘ï¸�

ðŸ“„ Footer                         [0 top-level items]
  No items in this location yet.
â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€
[Cancel]              [Save Changes]
```

---

### 3. Controls Per Item

Every item must render:
- **â ¿ Drag handle** (root-level `navigation_items` only, left side) â€” for drag-and-drop reordering (Phase 8). **Locked category rows do NOT get a drag handle** â€” use an empty spacer instead.
- **ðŸŸ¢ Active/Hidden Toggle** (green pill switch) â€” controls `is_active`. Does NOT save immediately â€” only updates local draft state.
- **Label** (bold) and **URL** (small text below it) â€” shown normally.
- **Type badge** â€” small colored tag showing the item type:
  - `CAT` â€” `category` (from Ranktraction sync, light blue)
  - `PAGE` â€” `custom_page` (amber)
  - `LINK` â€” `external` (gray)
  - `DROP` â€” `dropdown` (purple)
- **â–² / â–¼ Reorder buttons** â€” move item up/down within its group.
- **âœ�ï¸� Edit button** â€” for `custom_page` and `external` items. **BLOCKED for `category` type items** (replaced with ðŸ”’ icon).

> [!IMPORTANT]
> **The ðŸ—‘ï¸� Delete button is intentionally NOT shown in the Navigation Manager.** To delete a custom page, the admin must use the **Page Manager** (ðŸ—‘ï¸� button in the editor toolbar). This ensures the full cleanup (page record + canvas content + nav item) is always done together. Removing only the nav item from the manager would leave the page orphaned.

---

### 4. Item Type Behaviors

#### ðŸ”’ Ranktraction Categories (`itemType === 'category'`)
Items synced from the Ranktraction API have `itemType: 'category'`. These **must never** be edited or deleted from the Navigation Manager:
- The âœ�ï¸� Edit and ðŸ—‘ï¸� Delete buttons are **NOT shown** for these items.
- Instead, a **ðŸ”’ icon** with a tooltip appears: *"This category is synced from Ranktraction. It cannot be deleted from the Navigation Manager."*
- The URL field in the edit form is **disabled and grayed out** with the tooltip: *"Ranktraction URLs cannot be changed"*.

#### âœ�ï¸� Edit Flow (for `custom_page` and `external` items)
Clicking âœ�ï¸� expands inline edit fields in the same row:
- `Label` input
- `URL` input
- `Parent` select â€” shows **two grouped sections**:
  - **ðŸ”’ Ranktraction Categories** â€” all categories from the `categories` table (e.g., ã‚¢ã‚¯ãƒ†ã‚£ãƒ“ãƒ†ã‚£, ã‚°ãƒ«ãƒ¡). Selecting one sets `parentCategory` on the item, nesting it under that category dropdown in the live header.
  - **Custom Pages / Links** â€” all other root-level `navigation_items` in the same location. Selecting one sets `parentId`.
- âœ… **Done** button (saves to local draft state only â€” not to DB)
- âœ• **Cancel** button

> [!IMPORTANT]
> When a category is selected as parent â†’ set `parentCategory` field and clear `parentId` to null.
> When a nav item is selected as parent â†’ set `parentId` and clear `parentCategory` to null.
> These are mutually exclusive.

> [!WARNING]
> **SEO URL Change Warning:** If the user changes the `url` field to a different value AND the item has a real DB id (id > 0) AND itemType is NOT `category`, show a full-screen confirmation modal before applying the change:
> *"If you change the URL of this page and it is already ranking on Google, the ranking may be negatively affected. Are you sure you want to proceed?"*
> The modal has "Cancel" and "Yes, Change" buttons. Only apply the change to local state if the user clicks "Yes, Change".

#### ðŸ—‘ï¸� Delete Flow

> [!IMPORTANT]
> **The ðŸ—‘ï¸� Delete button is NOT present in the Navigation Manager.** See Section 3 for the rationale.
> Deletion of custom pages is handled exclusively through the **Page Manager** (the ðŸ—‘ï¸� icon-only button in the editor toolbar, available when editing a custom page).

The `DeletePageModal` component handles full cleanup:
- Deletes the `navigation_items` rows for the page (via `sourceId`)
- Deletes the `custom_pages` record
- Deletes the `homepage_content` canvas record

> [!CAUTION]
> Always confirm with a full popup modal before deleting. Never use an inline confirm. Show a warning if the page has child nav items that will also be removed.

---

### 5. The "Save Changes" Bulk API (`/api/navigation-items/bulk-save`)

When "Save Changes" is clicked, collect all changed + new items and deleted IDs, then call:

```
POST /api/navigation-items/bulk-save
```

Request body:
```json
{
  "items": [
    { "id": 1, "label": "About Us", "url": "/about", "menuLocation": "top_header", "parentId": null, "sortOrder": 0, "isActive": true },
    { "id": -1, "label": "New Page", "url": "/new", "menuLocation": "top_header", "parentId": 1, "sortOrder": 1, "isActive": true }
  ],
  "deletedIds": [5, 6]
}
```

> [!WARNING]
> **DO NOT use "delete all + re-insert all"** strategy. Because `navigation_items.id` is `SERIAL` (auto-increment), deleting and reinserting generates new IDs and breaks all `parent_id` references â€” every child becomes an orphan.

The API must use a **safe three-step approach:**
1. **UPSERT** items with real IDs (`id > 0`) â€” update `label`, `url`, `isActive`, `sortOrder`, `parentId`, `menuLocation`.
2. **INSERT** items with null/negative IDs â€” these are new items added during this editing session.
3. **DELETE** rows whose IDs appear in `deletedIds`.

After save, reload fresh state from DB to confirm.

---

### 6. No "Add Link" Button in the Navigation Manager

> [!CAUTION]
> **Do NOT add an "âž• Add Link" button to the Navigation Manager.** All new pages and links should be created exclusively through the **Create Page** flow (Phase 5's `CreatePageModal`). Adding links directly from the Navigation Manager bypasses the `custom_pages` table and the canvas initialization logic, creating orphan nav items with no backing page data. Keep the Navigation Manager focused on **managing existing items only** (reordering, toggling visibility, editing parent menus).

---

### 7. Displaying Ranktraction Categories as Locked Rows

On many sites, the Top Header contains categories synced from the Ranktraction API. These categories live in the `categories` table — **not** in `navigation_items`. This means `GET /api/navigation-items?all=1` will never return them.

> [!CAUTION]
> If you only display items from `navigation_items`, the Navigation Manager will appear incomplete — the admin will see their custom pages but be confused that all the category links they see on the live site are invisible.

**Solution — Option A: Categories in the `categories` table (other sites)**
1. Create a `GET /api/editor/categories` endpoint that fetches `id`, `japanese`, and `slug` from the `categories` table, filtered by `RANKTRACTION_SITE_ID`.
2. In the `NavigationManagerModal`, fetch categories **in parallel** with nav items on open.
3. In the `top_header` section, render all categories **before** the regular nav items as locked rows.

**Solution — Option B (housing-knowledge-portal): Categories in `navigation_items`**

> [!IMPORTANT]
> For `housing-knowledge-portal`, Ranktraction categories are stored **directly in `navigation_items`** as `itemType = 'category'` with `menuLocation = 'category_bar'`. They are NOT in the `categories` table. Do NOT create a `GET /api/editor/categories` API for this site.

1. Fetch ALL navigation items via `GET /api/navigation-items` in a single request.
2. Split results:
   - `category_bar` items with `itemType === 'category'` → **locked rows** (display in Top Header before custom pages)
   - `top_header` + `footer` items → **editable rows**
3. Include ALL items (category_bar + top_header + footer) in the `navItems` state so `isActive` toggle changes are tracked for all.
4. In the `top_header` section, render category_bar items **before** custom page items as locked rows:
   - **Active/Hidden PillToggle** (SVG-based, 36×20px) — IS shown and IS functional (saves `isActive` to DB)
   - `[CAT]` type badge (light blue)
   - 🔒 icon with tooltip: *"This category is synced from Ranktraction. It cannot be edited or deleted from the Navigation Manager."*
   - No ✏️ or 🗑️ buttons
5. The `topLevelCount` in the section header should count both locked categories AND regular nav items.

> [!NOTE]
> Category locked rows ARE included in the bulk-save payload (only `isActive` changes). Label, URL, and parent fields are never editable for category items.


---

### 8. Unsaved Changes Guard

- Show **"⚠️ You have unsaved changes"** in the modal header whenever local state differs from the initially loaded state.
- When the user clicks ❌ Close or Cancel while there are unsaved changes, show a browser confirmation:
  > *"You have unsaved changes. Are you sure you want to discard them?"*
  - If confirmed — discard draft, close modal.
  - If cancelled — keep modal open.

---

## Testing Steps
1. Open `/page_editor_admin` and click the **🧭 Nav Manager** button (icon-only, no text label) in the top header.
2. Confirm the modal opens with **Top Header** and **Footer** sections only (no category_bar for amamikke).
3. Confirm all Ranktraction-synced categories appear in Top Header as **locked rows** with 🔒 icon and `[CAT]` badge.
4. Confirm locked category rows have a **functional PillToggle** (SVG-based pill, 36×20px) for visibility, but **no ✏️ edit button, no 🗑️ delete button, and no drag handle**.
5. Click ✏️ on a `custom_page` item. Confirm the **Parent dropdown has two groups**: "🔒 Ranktraction Categories" and "Custom Pages / Links". Select a category and click Done — confirm the item is now nested under that category.
6. Click ✏️ and change the URL. Confirm the **SEO warning popup modal** appears. Click "Yes, Change URL" and confirm the URL is updated in the list.
7. Toggle visibility OFF on a `custom_page` item. Confirm the item shows greyed toggle. Confirm the live site still shows it BEFORE clicking "Save Changes".
8. Confirm there is **no 🗑️ Delete button** on any nav row. (Deleting pages is done via the Page Manager.)
9. Close the modal without saving. Confirm browser asks *"You have unsaved changes. Are you sure?"* and stays open if cancelled.
10. Click **"💾 Save Changes"**. Confirm modal reloads fresh data from DB. Confirm live website reflects all changes.
11. Confirm **"⚠️ You have unsaved changes"** appears in the modal header whenever any edit is made before saving.

---

## Editor Toolbar Button Rules

The following buttons in the `page_editor_admin` top toolbar must be **icon-only** (no text label). Use a fixed `34×34px` square button with a `title` attribute for tooltip on hover:

| Button | Icon | Tooltip |
|---|---|---|
| Page Settings | ⚙️ | "Edit Page Settings" |
| Delete Page | 🗑️ | "Delete Page" |
| Navigation Manager | 🧭 | "Navigation Manager" |

> [!IMPORTANT]
> **Visibility Rules:** The ⚙️ Page Settings and 🗑️ Delete Page buttons must **only** be visible when editing a custom page (they are hidden on core pages like `/` and `/about`). 
> However, the **🧭 Navigation Manager button must be visible globally on all pages**, because the navigation menus affect the entire site.

The **📋 Draft** and **🔵 Publish** buttons must keep their full text labels.

---

## Common Gotchas & Bug Fixes

1. **React Props Destructuring Error (`ReferenceError: onOpenNavManager is not defined`)**
   When adding new interactive buttons (like the 🧭 Navigation Manager) to existing UI components (like `HeaderActions`), ensure you strictly update **both** the TypeScript interface definitions AND the actual destructured arguments in the function signature. If you only update the TypeScript interface but forget to destructure the new prop (e.g., `function HeaderActions({ onSave, onOpenNavManager }: { ... })`), React will throw a `ReferenceError` when the button is clicked.

2. **Missing Categories / All 432 Categories Showing (Multi-Tenant `siteId` Mismatch)**
   For `housing-knowledge-portal`, ALL data (navigation_items, custom_pages, categories) must be filtered by `siteId = 'housing-knowledge-portal'`. The original code used `'cardinalhouse.jp'` as the SITE_ID for CMS tables — this caused the Navigation Manager to show all 432 categories from the shared database with no filter.

   **Fix applied:**
   - Unified all `SITE_ID` constants to `'housing-knowledge-portal'` in: `lib/navigation.ts`, `lib/homepage-content.ts`, `lib/custom-pages.ts`, `api/navigation-items/route.ts`, `api/navigation-items/bulk-save/route.ts`, `api/custom-pages/route.ts`.
   - Ran SQL migration to update existing DB rows in `navigation_items`, `custom_pages`, and `homepage_content` tables.
   - For this site, categories are stored in `navigation_items` (not the `categories` table), so the correct filter is by `siteId` on `navigation_items`.

3. **Stale Menu Data & Revalidation Time**
   By default, Next.js caches server-side page renders (e.g., `export const revalidate = 60`). If a user creates a new custom page or updates the menu via the Navigation Manager, the live website will continue to show the stale, cached menu until the cache expires. If this delay is unacceptable and changes must be visible instantly, you can disable the page-level caching by setting `export const revalidate = 0;` inside the root layout or relevant page routes (like `app/page.tsx`, `app/[slug]/page.tsx`, and `app/blog/page.tsx`). This forces Next.js to fetch the latest navigation items from the database dynamically on every request.

4. **Custom Page Name/URL Editing Confusion**
   The Navigation Manager is only meant to edit menu *links*, not the actual `custom_pages` DB records. If an admin edits the URL or Name of a custom page from within the Navigation Manager, it will only change the link text and href, causing a disconnect (and potential 404s) from the actual page data shown in the Page Switcher dropdown. To prevent this, you MUST disable the Label and URL input fields when `itemType === 'custom_page'` in the inline editor. Admins should only be allowed to change the Parent Menu dropdown for custom pages in the Nav Manager; actual page renames must happen in the `PageSettingsModal`.

---

## ✅ Phase 6 Completion Verification

Run this checklist to confirm Phase 6 is fully implemented:

- [x] `npx tsc --noEmit` passes with 0 errors
- [x] 🧭 Navigation Manager modal opens from the editor toolbar (icon-only button, visible globally)
- [x] Top Header + Footer sections visible (category_bar shown as locked rows inside Top Header — not a separate section)
- [x] Locked category rows display with `[CAT]` badge, 🔒 icon, and functional PillToggle (SVG-based, 36×20px)
- [x] Inline editing (✏️) works — label/URL disabled for `custom_page` items, Parent dropdown has two groups
- [x] PillToggle (SVG-based pill switch, 36×20px) shows/hides items — updates on child (↳) and subchild (↳↳) rows too
- [x] Ranktraction lock 🔒 prevents editing label/URL/parent of system-managed category items
- [x] No 🗑️ Delete button in Nav Manager — deletion handled exclusively by DeletePageModal
- [x] Custom page items have disabled label/URL fields (edit only via ⚙️ Page Settings)
- [x] All data filtered by `siteId = 'housing-knowledge-portal'` — no cross-site data leakage
- [x] Child (↳) and subchild (↳↳) rows display with their own PillToggle
- [x] Unsaved changes guard — ⚠️ badge in header + browser confirm on close
- [x] Bulk-save includes ALL items (category_bar + top_header + footer) so category isActive changes persist

> **Phase 6 is complete when ALL boxes above are checked.**
