# 画面設計書 11-会話詳細画面

## 概要

本ドキュメントは、Chatwootの会話詳細画面の設計仕様を記述したものです。会話詳細画面は、エージェントが顧客との個別の会話を詳細に閲覧し、メッセージの送受信やさまざまな会話管理操作を行うためのメイン画面です。

### 本画面の処理概要

**業務上の目的・背景**：
顧客からの問い合わせに対応するエージェントにとって、会話詳細画面は最も頻繁に使用する画面です。この画面では、過去のメッセージ履歴を確認しながら顧客とリアルタイムでコミュニケーションを取り、問い合わせの解決を図ります。また、会話に関するメタデータ（ラベル、担当者、ステータスなど）の管理も行い、チームでの効率的な顧客対応を実現します。

**画面へのアクセス方法**：
1. ダッシュボードホーム画面の会話一覧から特定の会話をクリック
2. インボックス別会話一覧から特定の会話をクリック
3. ラベル別/チーム別/カスタムビュー会話一覧から特定の会話をクリック
4. URL直接アクセス: `/app/accounts/:accountId/conversations/:conversation_id`

**主要な操作・処理内容**：
1. メッセージの閲覧: 過去のメッセージ履歴を時系列で表示、無限スクロールで過去メッセージを読み込み
2. メッセージの送信: テキストメッセージ、添付ファイル、メンション、プライベートノートの送信
3. 会話ステータスの変更: Open / Resolved / Pending / Snoozed間のステータス切り替え
4. エージェント割り当て: 会話を特定のエージェントまたはチームに割り当て
5. ラベル管理: 会話へのラベル付与・削除
6. 参加者管理: 会話への参加者追加・削除
7. 会話の優先度設定: Low / Medium / High / Urgentの優先度設定
8. メッセージの編集・削除・引用返信
9. コンタクトサイドバーの表示・非表示切り替え

**画面遷移**：
- 遷移元: ダッシュボードホーム画面、インボックス別会話一覧、ラベル別会話一覧、チーム別会話一覧、カスタムビュー会話一覧、メンション会話一覧、未対応会話一覧、参加中会話一覧、検索画面
- 遷移先: 連絡先詳細画面（サイドバー経由）、ヘルプセンター記事（記事挿入時）

**権限による表示制御**：
- administrator: 全機能へのフルアクセス
- agent: 基本的な会話操作（メッセージ送受信、ステータス変更、ラベル管理）
- conversation_manage: 会話の管理操作が可能
- conversation_unassigned_manage: 未割り当て会話の管理が可能
- conversation_participating_manage: 参加中会話の管理が可能

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 25 | 会話CRUD | 主機能 | 特定会話の詳細情報表示 |
| 43 | メッセージ送受信 | 主機能 | メッセージの送受信処理 |
| 44 | メッセージ翻訳 | 補助機能 | メッセージの多言語翻訳 |
| 46 | メッセージ削除・編集 | 補助機能 | メッセージの編集・削除 |
| 47 | メッセージ引用 | 補助機能 | 返信時の元メッセージ参照 |
| 48 | メディア対応 | 補助機能 | 画像・ファイルの送信 |
| 49 | メンション機能 | 補助機能 | @メンションによる通知 |
| 54 | プライベートノート | 補助機能 | 内部向けノートの追加 |
| 29 | 割り当て管理 | API連携 | エージェントの割り当て変更 |
| 30 | ラベル管理 | API連携 | ラベルの付与・削除 |
| 31 | 参加者管理 | API連携 | 会話参加者の追加・削除 |
| 32 | 添付ファイル管理 | API連携 | ファイルアップロード |
| 37 | スヌーズ | 補助機能 | 会話の一時停止 |
| 42 | SLAポリシー適用 | 補助機能 | SLA応答時間の追跡 |

## 画面種別

詳細

## URL/ルーティング

| ルート名 | パス | 説明 |
|---------|------|------|
| inbox_conversation | `/accounts/:accountId/conversations/:conversation_id` | 会話一覧からの詳細表示 |
| conversation_through_inbox | `/accounts/:accountId/inbox/:inbox_id/conversations/:conversation_id` | インボックス経由での詳細表示 |
| conversations_through_label | `/accounts/:accountId/label/:label/conversations/:conversation_id` | ラベル経由での詳細表示 |
| conversations_through_team | `/accounts/:accountId/team/:teamId/conversations/:conversationId` | チーム経由での詳細表示 |
| conversations_through_folders | `/accounts/:accountId/custom_view/:id/conversations/:conversation_id` | カスタムビュー経由での詳細表示 |
| conversation_through_mentions | `/accounts/:accountId/mentions/conversations/:conversationId` | メンション経由での詳細表示 |
| conversation_through_unattended | `/accounts/:accountId/unattended/conversations/:conversationId` | 未対応経由での詳細表示 |
| conversation_through_participating | `/accounts/:accountId/participating/conversations/:conversationId` | 参加中経由での詳細表示 |

## 入出力項目

### 入力項目（メッセージ送信フォーム）

| 項目名 | 型 | 必須 | 説明 |
|--------|-----|------|------|
| message_content | Text | 条件付き | メッセージ本文（添付ファイルのみの場合は任意） |
| attachments | File[] | No | 添付ファイル（複数可） |
| is_private | Boolean | No | プライベートノートフラグ（デフォルト: false） |
| cc_emails | String | No | CCメールアドレス（メールチャネルの場合） |
| bcc_emails | String | No | BCCメールアドレス（メールチャネルの場合） |
| template_id | Number | No | 使用するテンプレートID |
| quoted_message_id | Number | No | 引用元メッセージID |

### プロパティ（コンポーネントProps）

| 項目名 | 型 | 必須 | 説明 |
|--------|-----|------|------|
| conversationId | String/Number | Yes | 表示する会話のID |
| inboxId | String/Number | No | インボックスID |
| label | String | No | フィルタリング用ラベル名 |
| teamId | String | No | フィルタリング用チームID |
| conversationType | String | No | 会話タイプ（mention, unattended, participating） |
| foldersId | String/Number | No | カスタムビューID |

## 表示項目

### 会話ヘッダー

| 項目名 | 型 | 説明 |
|--------|-----|------|
| conversation_id | Number | 会話ID |
| contact_name | String | 連絡先名 |
| contact_avatar | String | 連絡先アバター画像URL |
| inbox_name | String | インボックス名 |
| inbox_icon | String | インボックスアイコン |
| status | String | 会話ステータス（open/resolved/pending/snoozed） |
| priority | String | 優先度（low/medium/high/urgent） |
| sla_status | Object | SLAステータス情報 |

### メッセージ一覧

| 項目名 | 型 | 説明 |
|--------|-----|------|
| id | Number | メッセージID |
| content | String | メッセージ本文 |
| message_type | Number | メッセージタイプ（0:incoming, 1:outgoing, 2:activity） |
| created_at | DateTime | 作成日時 |
| sender | Object | 送信者情報 |
| attachments | Array | 添付ファイル一覧 |
| private | Boolean | プライベートノートフラグ |
| status | String | メッセージステータス（sent/delivered/read/failed） |
| content_attributes | Object | 追加属性（翻訳、引用など） |

### コンタクトサイドバー

| 項目名 | 型 | 説明 |
|--------|-----|------|
| contact_info | Object | 連絡先の詳細情報 |
| custom_attributes | Object | カスタム属性 |
| conversation_labels | Array | 会話に付与されたラベル一覧 |
| previous_conversations | Array | 過去の会話履歴 |
| participants | Array | 会話参加者一覧 |

## イベント仕様

### 1-メッセージ送信

**トリガー**: ReplyBoxでのメッセージ送信ボタン押下またはEnterキー

**処理フロー**:
1. 入力内容のバリデーション
2. ペンディングメッセージの作成（UIに即座に表示）
3. MessageApi.create()でサーバーへ送信
4. 成功時: メッセージステータスをSENTに更新
5. 失敗時: メッセージステータスをFAILEDに更新、再送ボタン表示

**Vuexアクション**: `createPendingMessageAndSend`, `sendMessageWithData`

### 2-ステータス変更

**トリガー**: ヘッダーのステータス変更ドロップダウン選択

**処理フロー**:
1. 新しいステータスの選択
2. Resolved選択時は必須属性のチェック（設定されている場合）
3. ConversationApi.toggleStatus()でサーバーへ送信
4. ストアの会話ステータスを更新
5. 成功通知の表示

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

### 3-エージェント割り当て

**トリガー**: 割り当てドロップダウンからエージェント選択

**処理フロー**:
1. エージェントの選択
2. ConversationApi.assignAgent()でサーバーへ送信
3. ストアの割り当て情報を更新
4. 成功通知の表示

**Vuexアクション**: `assignAgent`, `setCurrentChatAssignee`

### 4-ラベル追加/削除

**トリガー**: サイドバーのラベル管理セクションでの操作

**処理フロー**:
1. ラベルの追加または削除操作
2. conversationLabelsストアの更新
3. サーバーへの同期

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

### 5-過去メッセージの読み込み

**トリガー**: メッセージ一覧の上部へのスクロール

**処理フロー**:
1. スクロール位置の検出（上部100px以内）
2. MessageApi.getPreviousMessages()で過去メッセージ取得
3. メッセージリストの先頭に追加
4. スクロール位置の調整（読み込み前の位置を維持）

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

### 6-メッセージ既読マーク

**トリガー**: 会話詳細画面の表示またはスクロール時

**処理フロー**:
1. ConversationApi.markMessageRead()でサーバーへ送信
2. agent_last_seen_atの更新
3. 未読カウントのリセット

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

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

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

| 操作（イベント） | 対象テーブル | 操作種別 | 概要 |
|----------------|-------------|---------|------|
| メッセージ送信 | messages | INSERT | 新規メッセージの作成 |
| メッセージ送信 | attachments | INSERT | 添付ファイルの保存（ある場合） |
| ステータス変更 | conversations | UPDATE | status, snoozed_untilの更新 |
| エージェント割り当て | conversations | UPDATE | assignee_idの更新 |
| チーム割り当て | conversations | UPDATE | team_idの更新 |
| ラベル追加 | conversation_labels | INSERT | ラベルの関連付け |
| ラベル削除 | conversation_labels | DELETE | ラベルの関連付け削除 |
| 参加者追加 | conversation_participants | INSERT | 参加者の追加 |
| 参加者削除 | conversation_participants | DELETE | 参加者の削除 |
| 優先度変更 | conversations | UPDATE | priorityの更新 |
| メッセージ既読 | conversations | UPDATE | agent_last_seen_atの更新 |
| メッセージ削除 | messages | UPDATE | content_typeをdeleted_messageに変更 |
| カスタム属性更新 | conversations | UPDATE | custom_attributesの更新 |

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

#### messages

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | conversation_id | 現在の会話ID | |
| INSERT | content | 入力されたメッセージ本文 | |
| INSERT | message_type | 1 (outgoing) | エージェント送信の場合 |
| INSERT | private | true/false | プライベートノートフラグ |
| INSERT | sender_type | 'User' | エージェントが送信 |
| INSERT | sender_id | 現在のユーザーID | |
| INSERT | content_attributes | {} | メタ情報（引用、翻訳など） |
| UPDATE | content_type | 'deleted_message' | メッセージ削除時 |

#### conversations

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| UPDATE | status | 0-3 | 0:open, 1:resolved, 2:pending, 3:snoozed |
| UPDATE | snoozed_until | DateTime/null | スヌーズ時の再開日時 |
| UPDATE | assignee_id | エージェントID/null | |
| UPDATE | team_id | チームID/null | |
| UPDATE | priority | low/medium/high/urgent/null | |
| UPDATE | agent_last_seen_at | 現在日時 | 既読マーク時 |
| UPDATE | custom_attributes | JSONオブジェクト | |

## メッセージ仕様

| メッセージID | 種別 | メッセージ内容 | 表示条件 |
|-------------|------|---------------|---------|
| CONVERSATION.CHANGE_STATUS | 成功 | ステータスが変更されました | ステータス変更成功時 |
| CONVERSATION.CANNOT_REPLY | 警告 | 返信ウィンドウが閉じています | 返信不可状態 |
| CONVERSATION.24_HOURS_WINDOW | 情報 | 24時間返信ウィンドウ | Facebook/Instagram/WhatsApp |
| CONVERSATION.TWILIO_WHATSAPP_CAN_REPLY | 情報 | 返信可能です | WhatsApp返信可能時 |
| CONVERSATION.SUCCESS_DELETE_CONVERSATION | 成功 | 会話が削除されました | 会話削除成功時 |
| CONVERSATION.FAIL_DELETE_CONVERSATION | エラー | 会話の削除に失敗しました | 会話削除失敗時 |
| CONVERSATION.PRIORITY.CHANGE_PRIORITY.SUCCESSFUL | 成功 | 優先度が変更されました | 優先度変更成功時 |

## 例外処理

| 例外状態 | 表示内容 | 処理 |
|---------|---------|------|
| 会話が見つからない | EmptyStateコンポーネント表示 | 会話一覧への導線を表示 |
| メッセージ送信失敗 | FAILEDステータス表示、再送ボタン | ユーザーが再送を選択可能 |
| 返信ウィンドウ外 | バナーで警告表示 | 返信ポリシーへのリンク表示 |
| 権限不足 | 該当操作ボタンが非表示または無効化 | |
| ネットワークエラー | エラー通知表示 | 自動リトライまたは手動リトライ |

## 備考

- WebSocket経由でリアルタイムメッセージ受信に対応
- メッセージはvue-virtual-scrollerを使用した仮想スクロールで表示
- Composition APIとOptions APIが混在（移行中）
- ダッシュボードアプリ（iframe埋め込み）のサポート
- Captain AI（ラベル提案機能）との統合

---

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

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

### 推奨読解順序

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

まず、会話とメッセージのデータ構造を理解することが重要です。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | conversations/getters.js | `app/javascript/dashboard/store/modules/conversations/getters.js` | getAllConversations, getSelectedChatで会話データの取得方法を理解 |
| 1-2 | conversation.js (API) | `app/javascript/dashboard/api/inbox/conversation.js` | ConversationApiクラスでAPIエンドポイントの構造を理解 |

**読解のコツ**: Vuexストアのgettersは、コンポーネントでmapGettersを使って呼び出されます。stateの構造とgettersの変換処理を把握してください。

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

処理の起点となるファイル・関数を特定します。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | ConversationView.vue | `app/javascript/dashboard/routes/dashboard/conversation/ConversationView.vue` | ルートコンポーネントとして全体構造を把握 |
| 2-2 | conversation.routes.js | `app/javascript/dashboard/routes/dashboard/conversation/conversation.routes.js` | ルーティング定義とprops渡しのパターン |

**主要処理フロー**:
1. **行14-55**: props定義でルートパラメータ受け取り
2. **行71-75**: mapGettersでchatList, currentChat取得
3. **行116-124**: mountedでagents取得とinitialize呼び出し
4. **行149-162**: findConversationで会話をリストから検索
5. **行163-186**: setActiveChatで選択会話をストアにセット

#### Step 3: 会話一覧コンポーネントを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | ChatList.vue | `app/javascript/dashboard/components/ChatList.vue` | 会話一覧の表示とフィルタリングロジック |

**主要処理フロー**:
- **行325-349**: conversationListのcomputed - フィルタリング条件に基づく会話リスト生成
- **行573-607**: fetchConversations, loadMoreConversations - ページネーション処理
- **行981-1033**: DynamicScrollerによる仮想スクロール実装

#### Step 4: 会話詳細表示コンポーネントを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | ConversationBox.vue | `app/javascript/dashboard/components/widgets/conversation/ConversationBox.vue` | 会話詳細のラッパーコンポーネント |
| 4-2 | MessagesView.vue | `app/javascript/dashboard/components/widgets/conversation/MessagesView.vue` | メッセージ一覧とReplyBoxの統合 |

**主要処理フロー (MessagesView)**:
- **行133-151**: getMessages, readMessages, unReadMessages - メッセージの分類
- **行389-418**: fetchPreviousMessages - 過去メッセージの無限スクロール読み込み
- **行351-383**: scrollToBottom - 未読メッセージまたは最新メッセージへのスクロール

#### Step 5: Vuexアクションを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | conversations/actions.js | `app/javascript/dashboard/store/modules/conversations/actions.js` | 会話に関するすべてのアクション |

**主要処理フロー**:
- **行34-42**: getConversation - 単一会話の取得
- **行192-207**: setActiveChat - 選択会話の設定と初期メッセージ取得
- **行280-316**: createPendingMessageAndSend, sendMessageWithData - メッセージ送信処理
- **行240-278**: toggleStatus - ステータス変更（カスタム属性更新を含む）

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

```
ConversationView.vue (ルートコンポーネント)
    |
    +-- ChatList.vue (会話一覧)
    |       |
    |       +-- ChatListHeader.vue (ヘッダー・フィルター)
    |       +-- ChatTypeTabs.vue (担当者タブ)
    |       +-- ConversationItem.vue (会話カード)
    |       +-- DynamicScroller (仮想スクロール)
    |
    +-- ConversationBox.vue (会話詳細)
    |       |
    |       +-- ConversationHeader.vue (会話ヘッダー)
    |       +-- MessagesView.vue (メッセージ表示)
    |       |       |
    |       |       +-- MessageList.vue (メッセージ一覧)
    |       |       +-- ReplyBox.vue (返信入力)
    |       |
    |       +-- DashboardAppFrame.vue (外部アプリ埋め込み)
    |
    +-- ConversationSidebar.vue (サイドバー)
            |
            +-- ContactPanel.vue (連絡先パネル)
```

### データフロー図

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

URL params          --> Vue Router                --> ConversationView.vue
(conversation_id)       (route matching)              (props injection)
                                                          |
                                                          v
                    --> Vuex Store                --> ChatList.vue
                        (conversations module)        (会話一覧表示)
                            |
                            v
                        getConversation()
                        setActiveChat()
                            |
                            v
currentChat         --> ConversationBox.vue       --> MessagesView.vue
(selected chat)         (会話詳細表示)               (メッセージ表示)
                                                          |
                                                          v
User input          --> ReplyBox.vue              --> MessageApi.create()
(message)               (メッセージ入力)              (サーバー送信)
                                                          |
                                                          v
                    --> Vuex mutations            --> UI更新
                        (ADD_MESSAGE)                 (新メッセージ表示)
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| 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` | ソース | 会話一覧コンポーネント |
| ConversationBox.vue | `app/javascript/dashboard/components/widgets/conversation/ConversationBox.vue` | ソース | 会話詳細ラッパー |
| MessagesView.vue | `app/javascript/dashboard/components/widgets/conversation/MessagesView.vue` | ソース | メッセージ表示・入力 |
| ConversationSidebar.vue | `app/javascript/dashboard/components/widgets/conversation/ConversationSidebar.vue` | ソース | コンタクトサイドバー |
| conversations/getters.js | `app/javascript/dashboard/store/modules/conversations/getters.js` | ソース | Vuex getters |
| conversations/actions.js | `app/javascript/dashboard/store/modules/conversations/actions.js` | ソース | Vuex actions |
| conversation.js | `app/javascript/dashboard/api/inbox/conversation.js` | ソース | API クライアント |
| MessageList.vue | `app/javascript/dashboard/components-next/message/MessageList.vue` | ソース | メッセージ一覧（next） |
| ReplyBox.vue | `app/javascript/dashboard/components/widgets/conversation/ReplyBox.vue` | ソース | 返信入力ボックス |
| ContactPanel.vue | `app/javascript/dashboard/routes/dashboard/conversation/ContactPanel.vue` | ソース | 連絡先パネル |
| globals.js | `app/javascript/dashboard/constants/globals.js` | 設定 | 定数定義 |
