# 画面設計書 14-カスタムビュー会話一覧

## 概要

本ドキュメントは、Chatwootのカスタムビュー会話一覧画面の設計仕様を記述したものです。ユーザーが保存したカスタムフィルタ（フォルダ）に基づいて会話をフィルタリングして表示する画面です。

### 本画面の処理概要

**業務上の目的・背景**：
エージェントやチームマネージャーは、業務に応じた複雑な条件で会話を検索することがよくあります。例えば「優先度が高く、特定のインボックスで、過去24時間以内に更新された未対応会話」といった条件です。カスタムビュー（フォルダ）機能は、これらの複合条件を保存し、ワンクリックで同じフィルタを再適用できるようにすることで、日常的な業務の効率化を実現します。

**画面へのアクセス方法**：
1. サイドバーの「Folders」セクションから保存済みカスタムビューをクリック
2. URL直接アクセス: `/app/accounts/:accountId/custom_view/:id`

**主要な操作・処理内容**：
1. カスタムフィルタによる会話一覧の表示
2. 保存済みフィルタ条件の適用
3. フィルタ条件の編集・更新
4. カスタムビューの削除
5. 会話の選択と詳細表示
6. 一括操作（複数会話のステータス変更、割り当て変更など）

**画面遷移**：
- 遷移元: ダッシュボードホーム画面（サイドバーのFoldersリンク）
- 遷移先: 会話詳細画面（会話クリック時）

**権限による表示制御**：
- administrator: 全機能へのフルアクセス、他ユーザーのカスタムビューも管理可能
- agent: 自分が作成したカスタムビューの閲覧・編集・削除
- conversation_manage: 会話の管理操作が可能

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 25 | 会話CRUD | 主機能 | カスタムフィルタによる会話一覧表示 |
| 79 | カスタムフィルタ | 補助機能 | 保存済みフィルタの適用 |

## 画面種別

一覧

## URL/ルーティング

| ルート名 | パス | 説明 |
|---------|------|------|
| folder_conversations | `/accounts/:accountId/custom_view/:id` | カスタムビュー会話一覧（詳細なし） |
| conversations_through_folders | `/accounts/:accountId/custom_view/:id/conversations/:conversation_id` | カスタムビュー会話一覧（詳細あり） |

## 入出力項目

### ルートパラメータ

| 項目名 | 型 | 必須 | 説明 |
|--------|-----|------|------|
| accountId | Number | Yes | アカウントID |
| id | String/Number | Yes | カスタムビュー（フォルダ）ID |
| conversation_id | Number | No | 表示する会話ID（詳細表示時） |

### コンポーネントProps

| 項目名 | 型 | 必須 | 説明 |
|--------|-----|------|------|
| foldersId | String/Number | Yes | カスタムビューID |
| conversationId | String/Number | No | 選択中の会話ID |

## 表示項目

### ヘッダー部

| 項目名 | 型 | 説明 |
|--------|-----|------|
| page_title | String | カスタムビュー名（activeFolder.nameから取得） |
| edit_filter_button | Button | フィルター編集モーダル表示ボタン |
| delete_folder_button | Button | カスタムビュー削除ボタン |
| add_folder_button | Button | 新規カスタムビュー作成ボタン |

### 会話一覧

| 項目名 | 型 | 説明 |
|--------|-----|------|
| conversation_card | Component | 会話カード（ConversationItem） |
| inbox_icon | Icon | インボックスのアイコン |
| contact_name | String | 連絡先名 |
| last_message_preview | String | 最新メッセージのプレビュー |
| last_activity_time | DateTime | 最終活動時刻 |
| unread_count | Number | 未読メッセージ数 |
| labels | Array | 付与されているラベル一覧 |
| assignee_avatar | Image | 担当者アバター（表示） |

### フィルター条件表示

| 項目名 | 型 | 説明 |
|--------|-----|------|
| applied_filters | Array | 適用されているフィルター条件一覧 |
| filter_tags | Component | フィルター条件のタグ表示 |

## イベント仕様

### 1-カスタムビュー選択

**トリガー**: サイドバーでカスタムビューをクリック

**処理フロー**:
1. Vue Routerでルートパラメータ変更を検知
2. ChatList.vueのwatchでactiveFolderの変更を検知
3. resetAndFetchData()を呼び出し
4. 会話リストをクリア
5. activeFolder.queryからフィルター条件を取得
6. fetchSavedFilteredConversations()でフィルタリングされた会話を取得

**Vuexアクション**: `fetchFilteredConversations`

### 2-フィルター編集

**トリガー**: フィルター編集ボタンのクリック

**処理フロー**:
1. onToggleAdvanceFiltersModal()で高度なフィルターモーダルを表示
2. initializeFolderToFilterModal()で既存のフィルター条件をモーダルに設定
3. ユーザーがフィルター条件を編集
4. onUpdateSavedFilter()でカスタムビューを更新

**Vuexアクション**: `customViews/update`

### 3-カスタムビュー削除

**トリガー**: 削除ボタンのクリック

**処理フロー**:
1. DeleteCustomViews.vueで削除確認モーダルを表示
2. 確認後、customViews/deleteアクションを実行
3. openLastItemAfterDeleteInFolder()で別のカスタムビューまたはホームへ遷移

**Vuexアクション**: `customViews/delete`

### 4-新規カスタムビュー保存

**トリガー**: 現在のフィルター条件から新規保存

**処理フロー**:
1. onClickOpenAddFoldersModal()でSaveCustomViewモーダルを表示
2. フォルダ名を入力
3. customViews/createアクションを実行
4. openLastSavedItemInFolder()で新しいカスタムビューへ遷移

**Vuexアクション**: `customViews/create`

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

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

| 操作（イベント） | 対象テーブル | 操作種別 | 概要 |
|----------------|-------------|---------|------|
| カスタムビュー取得 | custom_filters | SELECT | 保存済みフィルター条件の取得 |
| 会話一覧取得 | conversations | SELECT | フィルター条件による会話検索 |
| カスタムビュー更新 | custom_filters | UPDATE | フィルター条件の更新 |
| カスタムビュー削除 | custom_filters | DELETE | カスタムビューの削除 |

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

#### custom_filters

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | * | id = :foldersId, filter_type = 'conversation' | |
| UPDATE | name | ユーザー入力値 | |
| UPDATE | query | filterQueryGenerator()の結果 | JSONオブジェクト |
| DELETE | * | id = :foldersId | |

#### conversations（SELECT）

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | * | query条件に基づくフィルタリング | POSTリクエスト |

## メッセージ仕様

| メッセージID | 種別 | メッセージ内容 | 表示条件 |
|-------------|------|---------------|---------|
| CHAT_LIST.LIST.404 | 情報 | 会話が見つかりません | フィルタリング結果が0件 |
| CHAT_LIST.EOF | 情報 | すべての会話を表示しました | リスト末尾到達時 |
| FILTER.CUSTOM_VIEWS.DELETE.API.SUCCESS_MESSAGE | 成功 | フォルダが削除されました | 削除成功時 |
| FILTER.CUSTOM_VIEWS.UPDATE.API.SUCCESS_MESSAGE | 成功 | フォルダが更新されました | 更新成功時 |

## 例外処理

| 例外状態 | 表示内容 | 処理 |
|---------|---------|------|
| カスタムビューが存在しない | ホーム画面へリダイレクト | |
| フィルタリング結果が0件 | 「会話が見つかりません」メッセージ | |
| ネットワークエラー | ローディング継続または再試行 | |

## 備考

- カスタムビューのqueryはJSON形式で保存され、filterQueryGenerator()で変換される
- 担当者タブは非表示（hasAppliedFiltersOrActiveFoldersがtrueの場合）
- カスタムビュー作成にはフィルターモーダルで条件を設定してから保存する
- フィルター条件の種類: ステータス、担当者、インボックス、ラベル、チーム、優先度、言語、国、カスタム属性など

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | conversation.routes.js | `app/javascript/dashboard/routes/dashboard/conversation/conversation.routes.js` | 行110-132でフォルダ関連ルートを確認 |
| 1-2 | customViews/index.js | `app/javascript/dashboard/store/modules/customViews/index.js` | カスタムビューのストア構造 |

**読解のコツ**: foldersId propsとactiveFolder computedの関係を理解してください。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | ConversationView.vue | `app/javascript/dashboard/routes/dashboard/conversation/ConversationView.vue` | 行51-54でfoldersId propsの定義を確認 |

**主要処理フロー**:
1. **行51-54**: foldersId propsの定義（デフォルト0）
2. **行199-208**: ChatListへのfolders-id propsの受け渡し

#### Step 3: フィルタリングロジックを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | ChatList.vue | `app/javascript/dashboard/components/ChatList.vue` | カスタムビューによるフィルタリング処理 |

**主要処理フロー**:
- **行80**: foldersId propsの定義
- **行174-188**: activeFolder, activeFolderName, hasActiveFolders computedの定義
- **行319-322**: pageTitleでフォルダ名を表示
- **行340-346**: conversationListでフォルダのフィルター条件を適用
- **行413-422**: fetchSavedFilteredConversationsでフィルタリング
- **行518-548**: initializeFolderToFilterModalで編集時の初期値設定
- **行885-890**: watch(activeFolder)で変更時にresetAndFetchData()呼び出し

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

```
ConversationView.vue
    |
    +-- ChatList.vue (foldersId props受け渡し)
    |       |
    |       +-- activeFolder (computed)
    |       |       folders.find(view => view.id === foldersId)
    |       |
    |       +-- hasActiveFolders (computed)
    |       |       Boolean(activeFolder && foldersId !== 0)
    |       |
    |       +-- fetchSavedFilteredConversations()
    |       |       |
    |       |       +-- ConversationApi.filter({ queryData: payload })
    |       |
    |       +-- SaveCustomView.vue (新規保存モーダル)
    |       |
    |       +-- DeleteCustomViews.vue (削除モーダル)
    |       |
    |       +-- ConversationFilter.vue (フィルター編集)
    |
    +-- ConversationBox.vue (選択時)
```

### データフロー図

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

URL params          --> Vue Router                --> ConversationView.vue
(/custom_view/:id)      (route matching)              (foldersId props)
                                                          |
                                                          v
                    --> ChatList.vue              --> activeFolder
                        (foldersId props)             (customViews getterから取得)
                                                          |
                                                          v
                    --> activeFolder.query        --> フィルター条件
                        ({ payload: [...] })
                                                          |
                                                          v
                    --> ConversationApi.filter()  --> API Request
                        (queryData)                   (POST /conversations/filter)
                                                          |
                                                          v
                    --> Vuex Store                --> 会話一覧表示
                        (allConversations)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| ConversationView.vue | `app/javascript/dashboard/routes/dashboard/conversation/ConversationView.vue` | ソース | ルートコンポーネント |
| conversation.routes.js | `app/javascript/dashboard/routes/dashboard/conversation/conversation.routes.js` | ソース | フォルダルート定義 |
| ChatList.vue | `app/javascript/dashboard/components/ChatList.vue` | ソース | 会話一覧・フィルタリング |
| conversation.js | `app/javascript/dashboard/api/inbox/conversation.js` | ソース | API（filter メソッド） |
| customViews/index.js | `app/javascript/dashboard/store/modules/customViews/index.js` | ソース | カスタムビューストア |
| SaveCustomView.vue | `app/javascript/dashboard/components-next/filter/SaveCustomView.vue` | ソース | 保存モーダル |
| DeleteCustomViews.vue | `app/javascript/dashboard/routes/dashboard/customviews/DeleteCustomViews.vue` | ソース | 削除モーダル |
| ConversationFilter.vue | `app/javascript/dashboard/components-next/filter/ConversationFilter.vue` | ソース | フィルター編集モーダル |
| filterQueryGenerator.js | `app/javascript/dashboard/helper/filterQueryGenerator.js` | ソース | フィルタークエリ生成 |
