# 画面設計書 91-ポータル一覧画面

## 概要

本ドキュメントは、Chatwootのヘルプセンター機能における「ポータル一覧画面」の設計仕様を記述したものである。

### 本画面の処理概要

ポータル一覧画面は、ヘルプセンター機能のエントリーポイントとして機能し、ユーザーを適切なポータルの記事一覧ページへ自動的にルーティングする画面である。

**業務上の目的・背景**：企業の顧客サポート業務において、セルフサービス型のヘルプセンターポータルを効率的に管理することが求められる。本画面は、ユーザーが過去に訪問したポータルや、デフォルトのポータルへ素早くアクセスできるようにする中継点として機能する。複数のポータルを持つ企業において、ユーザーの最終アクセス履歴に基づく自動ルーティングにより、作業効率を向上させる。

**画面へのアクセス方法**：サイドナビゲーションの「ヘルプセンター」メニューをクリックするか、URL `/app/accounts/:accountId/portals` に直接アクセスする。

**主要な操作・処理内容**：
1. 画面表示時にポータル一覧をAPIから取得
2. UISettings（last_active_portal_slug, last_active_locale_code）を参照して最終アクティブポータルを特定
3. 該当ポータルが存在する場合、記事一覧画面へ自動リダイレクト
4. ポータルが存在しない場合、新規作成画面へリダイレクト

**画面遷移**：
- 遷移元：サイドナビゲーション、ダッシュボード
- 遷移先：記事一覧画面（portals_articles_index）、カテゴリ一覧画面（portals_categories_index）、ロケール一覧画面（portals_locales_index）、設定画面（portals_settings_index）、新規作成画面（portals_new）

**権限による表示制御**：administrator、agent、knowledge_base_manage権限を持つユーザーのみアクセス可能。HELP_CENTERフィーチャーフラグが有効である必要がある。

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 151 | ポータル | 主機能 | ヘルプセンターポータルの一覧取得 |
| 152 | ポータルアーカイブ | 補助機能 | ポータルの無効化（非表示化） |

## 画面種別

一覧（ローディング・リダイレクト画面）

## URL/ルーティング

| 項目 | 値 |
|------|-----|
| パス | `/app/accounts/:accountId/portals/:navigationPath` |
| ルート名 | `portals_index` |
| コンポーネント | `PortalsIndexPage.vue` |

## 入出力項目

### 入力項目（URLパラメータ）

| 項目名 | 型 | 必須 | 説明 |
|--------|-----|------|------|
| accountId | Number | Yes | アカウントID |
| navigationPath | String | No | 遷移先のルート名 |

### 出力項目（リダイレクト先パラメータ）

| 項目名 | 型 | 説明 |
|--------|-----|------|
| portalSlug | String | ポータルのスラッグ |
| locale | String | デフォルトロケール |

## 表示項目

| 項目名 | 型 | 説明 |
|--------|-----|------|
| Spinner | Component | ローディングインジケーター |

## イベント仕様

### 1-画面表示時の自動ルーティング処理

**トリガー**: コンポーネントのマウント時（onMounted）

**処理フロー**:
1. `portals/index` アクションをディスパッチしてポータル一覧を取得
2. UISettingsから `last_active_portal_slug` と `last_active_locale_code` を取得
3. 以下の優先順位でルーティング先を決定:
   - 最終アクティブポータルが存在する場合 → そのポータルの記事一覧へ
   - ポータルが1つ以上存在する場合 → 最初のポータルの記事一覧へ
   - ポータルが存在しない場合 → 新規作成画面へ
4. navigationPathパラメータが有効なルート名の場合、そのルートへ遷移

**有効なnavigationPath値**:
- `portals_articles_index`
- `portals_categories_index`
- `portals_locales_index`
- `portals_settings_index`

## データベース更新仕様

### 操作別データベース影響一覧

| 操作（イベント） | 対象テーブル | 操作種別 | 概要 |
|----------------|-------------|---------|------|
| ポータル一覧取得 | portals | SELECT | アカウントに紐づくポータル一覧を取得 |

### テーブル別更新項目詳細

#### portals

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | id, name, slug, config, archived, custom_domain | account_id = 現在のアカウントID | 全カラム取得 |

## メッセージ仕様

| 種別 | メッセージキー | メッセージ内容 | 表示条件 |
|------|--------------|--------------|----------|
| ローディング | - | （スピナー表示のみ） | ポータル取得中 |

## 例外処理

| 例外状況 | 処理内容 |
|---------|---------|
| ポータル取得失敗 | エラーがスローされ、ローディング状態が継続 |
| 権限不足 | ルートガードによりアクセス拒否 |
| フィーチャーフラグ無効 | ルートガードによりアクセス拒否 |

## 備考

- 本画面は一時的なローディング・ルーティング画面であり、実質的なUIはスピナーのみ
- UISettingsはlocalStorageに保存されるユーザー設定
- フィーチャーフラグ `HELP_CENTER` が有効である必要がある

---

## コードリーディングガイド

本画面を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

ポータルのデータ構造とVuexストアの状態管理を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | Portal (Model) | `app/models/portal.rb` | ポータルのスキーマ定義（id, name, slug, config, archived等） |
| 1-2 | index.js | `app/javascript/dashboard/store/modules/helpCenterPortals/index.js` | Vuexストアの初期状態（portals.byId, allIds, uiFlags） |
| 1-3 | getters.js | `app/javascript/dashboard/store/modules/helpCenterPortals/getters.js` | ポータル取得用ゲッター（allPortals, portalBySlug等） |

**読解のコツ**: Vuexストアは正規化されたデータ構造（byIdとallIds）を採用しており、ゲッターでデータを再構成する。

#### Step 2: エントリーポイントを理解する

画面のエントリーポイントとルーティング処理を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | PortalsIndexPage.vue | `app/javascript/dashboard/routes/dashboard/helpcenter/pages/PortalsIndexPage.vue` | 画面コンポーネントのメイン処理 |
| 2-2 | helpcenter.routes.js | `app/javascript/dashboard/routes/dashboard/helpcenter/helpcenter.routes.js` | ルート定義とmeta情報 |

**主要処理フロー**:
1. **62-64行目**: performRouting関数でポータル取得とルーティングを実行
2. **45-59行目**: routeToLastActivePortal関数で遷移先を決定
3. **24-43行目**: generateRouterParams関数でUISettingsから最終アクティブポータルを取得
4. **16-18行目**: isPortalPresent関数でポータルの存在確認

#### Step 3: Vuex Actionsを理解する

ポータル取得のAPI呼び出し処理を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | actions.js | `app/javascript/dashboard/store/modules/helpCenterPortals/actions.js` | indexアクションの実装 |
| 3-2 | portals.js (API) | `app/javascript/dashboard/api/helpCenter/portals.js` | APIクライアントの実装 |

**主要処理フロー**:
- **7-21行目**: indexアクション - ポータル一覧取得とストアへのコミット

#### Step 4: バックエンドAPIを理解する

RailsコントローラーでのAPI処理を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | portals_controller.rb | `app/controllers/api/v1/accounts/portals_controller.rb` | indexアクションの実装 |

**主要処理フロー**:
- **8-10行目**: indexアクション - Current.account.portalsで全ポータル取得

### プログラム呼び出し階層図

```
PortalsIndexPage.vue
    │
    ├─ onMounted()
    │      └─ performRouting()
    │             ├─ store.dispatch('portals/index')
    │             │      └─ actions.index()
    │             │             └─ portalAPIs.get()
    │             │                    └─ GET /api/v1/accounts/:id/portals
    │             │                           └─ PortalsController#index
    │             │
    │             └─ routeToLastActivePortal()
    │                    ├─ generateRouterParams()
    │                    │      └─ uiSettings (last_active_portal_slug)
    │                    │
    │                    └─ router.replace()
    │
    └─ useUISettings()
           └─ localStorage (ui_settings)
```

### データフロー図

```
[入力]                    [処理]                           [出力]

accountId ─────────────▶ PortalsController#index ─────▶ portals[]
                              │
                              ▼
UISettings ─────────────▶ generateRouterParams() ───▶ { portalSlug, locale }
(localStorage)                │
                              ▼
                         routeToLastActivePortal()
                              │
                              ▼
                         router.replace() ─────────▶ 遷移先画面
                              │
                              ├─▶ portals_articles_index
                              ├─▶ portals_categories_index
                              ├─▶ portals_locales_index
                              ├─▶ portals_settings_index
                              └─▶ portals_new
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| PortalsIndexPage.vue | `app/javascript/dashboard/routes/dashboard/helpcenter/pages/PortalsIndexPage.vue` | ソース | 画面コンポーネント |
| helpcenter.routes.js | `app/javascript/dashboard/routes/dashboard/helpcenter/helpcenter.routes.js` | ソース | ルート定義 |
| index.js | `app/javascript/dashboard/store/modules/helpCenterPortals/index.js` | ソース | Vuexストア定義 |
| actions.js | `app/javascript/dashboard/store/modules/helpCenterPortals/actions.js` | ソース | Vuexアクション |
| getters.js | `app/javascript/dashboard/store/modules/helpCenterPortals/getters.js` | ソース | Vuexゲッター |
| portals.js | `app/javascript/dashboard/api/helpCenter/portals.js` | ソース | APIクライアント |
| portals_controller.rb | `app/controllers/api/v1/accounts/portals_controller.rb` | ソース | APIコントローラー |
| portal.rb | `app/models/portal.rb` | ソース | モデル定義 |
| Spinner.vue | `app/javascript/dashboard/components-next/spinner/Spinner.vue` | ソース | ローディングコンポーネント |
| useUISettings.js | `app/javascript/dashboard/composables/useUISettings.js` | ソース | UI設定composable |
