# 機能設計書 61-CMSページ表示

## 概要

本ドキュメントは、CMSページ表示機能の設計仕様を定義する。CMS（Content Management System）で管理されたコンテンツページ（利用規約、プライバシーポリシー等）をアプリ内で表示する機能を提供する。

### 本機能の処理概要

**業務上の目的・背景**：ECサイトでは利用規約やプライバシーポリシー、会社情報など、法的に必要な情報やマーケティング目的のコンテンツを顧客に提供する必要がある。これらのコンテンツはバックエンドのCMSで一元管理され、アプリとWebで共通のコンテンツを配信することで運用効率を高めている。

**機能の利用シーン**：
- ユーザーがナビゲーションドロワーからCMSページ一覧を開き、特定のページ（利用規約、プライバシーポリシー等）を閲覧する場合
- フッターや設定画面から各種情報ページへアクセスする場合
- 商品購入前に利用規約を確認する場合

**主要な処理内容**：
1. CMSページIDを指定してGraphQL APIを呼び出し、ページ詳細を取得
2. 取得したHTMLコンテンツをユーザーの選択言語（ロケール）に基づいてフィルタリング
3. HTMLコンテンツをFlutterウィジェットとしてレンダリング・表示

**関連システム・外部連携**：
- Bagisto GraphQL API（CMSページ詳細取得）
- flutter_widget_from_html パッケージ（HTMLレンダリング）

**権限による制御**：認証状態に関わらず、すべてのユーザーが閲覧可能。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 32 | CMSコンテンツ画面 | 主画面 | CMS管理されたコンテンツページ（利用規約、プライバシーポリシー等）の表示 |

## 機能種別

データ取得・表示処理（READ操作）

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| id | int | Yes | CMSページのID | 正の整数であること |
| title | String | No | 画面タイトル表示用 | - |
| index | int | No | 一覧でのインデックス | - |

### 入力データソース

- 画面遷移時にCmsDataContentオブジェクトとして渡されるルート引数
- ナビゲーションドロワーのCMSページ一覧から選択

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| id | String | CMSページID |
| name | String | ページ名 |
| htmlContent | String | HTMLコンテンツ本文 |
| locale | String | コンテンツの言語コード |

### 出力先

- CmsContent画面にHTMLコンテンツをレンダリング表示

## 処理フロー

### 処理シーケンス

```
1. 画面初期化（initState）
   └─ CmsBlocにFetchCmsDataEventを発行
2. CmsBlocでイベント処理
   └─ CmsRepositoryのcallCmsDataメソッドを呼び出し
3. APIクライアント呼び出し
   └─ GraphQL経由でcmsPage queryを実行
4. レスポンス処理
   └─ CmsPageモデルにデシリアライズ
5. 状態更新
   └─ FetchCmsDataState.successを発行
6. UI更新
   └─ BlocConsumerで状態を監視し、HTMLコンテンツを表示
7. ロケールフィルタリング
   └─ ユーザーの選択言語に対応するtranslationsを抽出
8. HTMLレンダリング
   └─ HtmlWidgetでHTMLコンテンツを表示
```

### フローチャート

```mermaid
flowchart TD
    A[画面初期化] --> B[FetchCmsDataEvent発行]
    B --> C[CmsBloc.mapEventToState]
    C --> D[CmsRepository.callCmsData]
    D --> E[ApiClient.getCmsPageDetails]
    E --> F{API成功?}
    F -->|Yes| G[CmsPageモデル生成]
    F -->|No| H[エラー状態発行]
    G --> I[FetchCmsDataState.success]
    H --> J[FetchCmsDataState.fail]
    I --> K[ロケールフィルタリング]
    K --> L[HTMLレンダリング]
    J --> M[EmptyDataView表示]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-61-01 | ロケール優先 | ユーザーの選択言語に対応するtranslationsを優先表示 | 常時 |
| BR-61-02 | フォールバック | 選択言語のtranslationsがない場合は最初のtranslationsを使用 | 選択言語のコンテンツがない場合 |

### 計算ロジック

該当なし

## データベース操作仕様

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

本機能はGraphQL APIを通じてバックエンドのデータを読み取るのみで、クライアント側でのデータベース操作は行わない。

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| CMSページ取得 | cms_pages | SELECT | 指定IDのCMSページ情報を取得 |
| 翻訳取得 | cms_page_translations | SELECT | ページのローカライズされたコンテンツを取得 |

### テーブル別操作詳細

#### cms_pages（バックエンド）

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | id | WHERE id = 指定ID | 主キー |

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | API通信エラー | ネットワーク接続失敗 | FetchCmsDataState.failを発行、EmptyDataView表示 |
| - | データ不存在 | 指定IDのCMSページが存在しない | EmptyDataView表示 |
| - | パースエラー | JSONデシリアライズ失敗 | catch節でエラーログ出力、fail状態発行 |

### リトライ仕様

自動リトライは実装されていない。ユーザーは画面を再読み込みする必要がある。

## トランザクション仕様

読み取り専用のため、トランザクション制御は不要。

## パフォーマンス要件

- CMSページ詳細取得: 3秒以内
- プリフェッチ有効時はキャッシュを使用

## セキュリティ考慮事項

- 認証不要で閲覧可能なパブリックコンテンツ
- HTMLコンテンツはサニタイズされてレンダリング
- XSS対策としてflutter_widget_from_htmlパッケージが適切にエスケープ処理

## 備考

- CMSコンテンツはBagistoバックエンド管理画面で編集可能
- 多言語対応はtranslationsフィールドで実現

---

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

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

### 推奨読解順序

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

まず、CMSページのデータ構造を理解することが重要。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | cms_details.dart | `lib/screens/cms_screen/data_model/cms_details.dart` | CmsPageクラス、Translationsクラスの構造を理解 |
| 1-2 | cms_model.dart | `lib/screens/cms_screen/data_model/cms_model.dart` | CMS一覧用のデータモデル（補助的な理解） |

**読解のコツ**:
- `@JsonSerializable()`アノテーションでJSONシリアライズを自動生成
- `translations`リストには多言語コンテンツが格納される
- **17-31行目**: CmsPageクラスでid、translationsを保持
- **33-51行目**: Translationsクラスでid、name、htmlContent、localeを保持

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

処理の起点となる画面ファイルを確認。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | cms_view.dart | `lib/screens/cms_screen/cms_view.dart` | CmsContentウィジェット、initStateでのイベント発行 |

**主要処理フロー**:
1. **13-20行目**: CmsContentウィジェットの定義、id/title/indexをコンストラクタで受け取る
2. **25-28行目**: initStateでCmsBlocを取得し、FetchCmsDataEventを発行
3. **44-49行目**: BlocConsumerでCmsBaseStateを監視
4. **77-90行目**: _cmsContentメソッドでHTMLコンテンツをレンダリング

#### Step 3: イベント・状態管理を理解する

BLoCパターンによる状態管理の仕組みを確認。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | cms_event.dart | `lib/screens/cms_screen/bloc/cms_event.dart` | FetchCmsDataEventの定義 |
| 3-2 | cms_state.dart | `lib/screens/cms_screen/bloc/cms_state.dart` | CmsStatus列挙型、FetchCmsDataStateの定義 |
| 3-3 | cms_bloc.dart | `lib/screens/cms_screen/bloc/cms_bloc.dart` | イベントからAPIを呼び出し、状態を発行する処理 |

**主要処理フロー**:
- **cms_event.dart 14-19行目**: FetchCmsDataEventでページIDを保持
- **cms_state.dart 16行目**: CmsStatus列挙型でsuccess/failを定義
- **cms_state.dart 20-26行目**: FetchCmsDataStateでcmsDataまたはerrorを保持
- **cms_bloc.dart 20-31行目**: mapEventToStateでFetchCmsDataEventを処理、成功/失敗に応じた状態を発行

#### Step 4: リポジトリ層を理解する

API呼び出しの抽象化層を確認。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | cms_repository.dart | `lib/screens/cms_screen/bloc/cms_repository.dart` | CmsRepositoryインターフェースと実装 |

**主要処理フロー**:
- **13-15行目**: CmsRepository抽象クラスでcallCmsDataメソッドを定義
- **16-28行目**: CmsRepositoryImpでApiClient().getCmsPageDetails(id)を呼び出し

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

```
CmsContent (cms_view.dart)
    │
    ├─ initState
    │      └─ CmsBloc.add(FetchCmsDataEvent)
    │
    └─ BlocConsumer<CmsBloc, CmsBaseState>
           │
           CmsBloc (cms_bloc.dart)
               │
               └─ mapEventToState
                      │
                      CmsRepository.callCmsData
                          │
                          ApiClient.getCmsPageDetails
                              │
                              GraphQL Query: cmsPage
```

### データフロー図

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

CMSページID ───────▶ FetchCmsDataEvent ───────▶ CmsBloc
                                                    │
                                                    ▼
                                           CmsRepository.callCmsData
                                                    │
                                                    ▼
                                           ApiClient.getCmsPageDetails
                                                    │
                                                    ▼
                                           GraphQL API (cmsPage query)
                                                    │
                                                    ▼
                                           CmsPage Model ───────▶ FetchCmsDataState.success
                                                                        │
                                                                        ▼
                                           ロケールフィルタリング ───▶ HtmlWidget表示
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| cms_view.dart | `lib/screens/cms_screen/cms_view.dart` | ソース | CMSコンテンツ表示画面 |
| cms_bloc.dart | `lib/screens/cms_screen/bloc/cms_bloc.dart` | ソース | 状態管理BLoC |
| cms_event.dart | `lib/screens/cms_screen/bloc/cms_event.dart` | ソース | イベント定義 |
| cms_state.dart | `lib/screens/cms_screen/bloc/cms_state.dart` | ソース | 状態定義 |
| cms_repository.dart | `lib/screens/cms_screen/bloc/cms_repository.dart` | ソース | API呼び出しリポジトリ |
| cms_details.dart | `lib/screens/cms_screen/data_model/cms_details.dart` | ソース | CMSページデータモデル |
| cms_model.dart | `lib/screens/cms_screen/data_model/cms_model.dart` | ソース | CMS一覧データモデル |
| cms_item_list.dart | `lib/screens/cms_screen/widgets/cms_item_list.dart` | ソース | CMS一覧ウィジェット |
| index.dart | `lib/screens/cms_screen/utils/index.dart` | ソース | エクスポート集約 |
| api_client.dart | `lib/services/api_client.dart` | ソース | GraphQL APIクライアント |
| mutation_query.dart | `lib/services/mutation_query.dart` | ソース | GraphQLクエリ定義 |
