# 画面設計書 93-記事一覧画面

## 概要

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

### 本画面の処理概要

記事一覧画面は、ヘルプセンターポータル内の記事を一覧表示し、フィルタリング・検索・ページネーションを行う画面である。記事のステータス管理や新規作成への導線も提供する。

**業務上の目的・背景**：カスタマーサポート担当者やナレッジベース管理者が、顧客向けのヘルプ記事を効率的に管理するための中心的な画面である。複数のフィルタリング条件（ステータス、カテゴリ、ロケール、著者）を組み合わせることで、大量の記事の中から目的の記事を素早く見つけることができる。また、タブによる記事ステータスの切り替えにより、下書き記事の管理やアーカイブ記事の確認が容易に行える。

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

**主要な操作・処理内容**：
1. 記事一覧を取得して表示（ページネーション対応）
2. タブによるステータスフィルタリング（全記事/自分の記事/下書き/公開済/アーカイブ）
3. カテゴリ別フィルタリング
4. ロケール切り替え
5. 記事カードクリックで詳細・編集画面へ遷移
6. 「New Article」ボタンで記事作成画面へ遷移

**画面遷移**：
- 遷移元：ポータル一覧画面、サイドナビゲーション、カテゴリ一覧画面
- 遷移先：記事新規作成画面（portals_articles_new）、記事編集画面（portals_articles_edit）

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

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 154 | ポータル記事 | 主機能 | ヘルプ記事の一覧表示 |
| 153 | ポータルカテゴリ | 補助機能 | カテゴリによるフィルタリング |

## 画面種別

一覧

## URL/ルーティング

| 項目 | 値 |
|------|-----|
| パス | `/app/accounts/:accountId/portals/:portalSlug/:locale/:categorySlug?/articles/:tab?` |
| ルート名 | `portals_articles_index` |
| コンポーネント | `PortalsArticlesIndexPage.vue` |

## 入出力項目

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

| 項目名 | 型 | 必須 | 説明 |
|--------|-----|------|------|
| accountId | Number | Yes | アカウントID |
| portalSlug | String | Yes | ポータルのスラッグ |
| locale | String | Yes | ロケールコード（例：en, ja） |
| categorySlug | String | No | カテゴリスラッグ（カテゴリ絞り込み時） |
| tab | String | No | タブ種別（mine/draft/archived/空） |

### 出力項目

| 項目名 | 型 | 説明 |
|--------|-----|------|
| articles | Array | 記事一覧 |
| categories | Array | カテゴリ一覧 |
| allowedLocales | Array | 利用可能ロケール一覧 |
| meta | Object | ページネーション・件数情報 |

## 表示項目

### ヘッダーコントロール

| 項目名 | 型 | 説明 |
|--------|-----|------|
| タブ | Tab | All articles / Mine / Draft / Archived |
| カテゴリセレクト | Select | カテゴリによるフィルタリング |
| ロケールセレクト | Select | ロケール切り替え |
| 新規作成ボタン | Button | "New article" |

### 記事カード（ArticleCard）

| 項目名 | 型 | 説明 |
|--------|-----|------|
| id | Number | 記事ID |
| title | String | 記事タイトル |
| status | String | ステータス（draft/published/archived） |
| updatedAt | DateTime | 最終更新日時 |
| author | Object | 著者情報 |
| category | Object | カテゴリ情報 |
| views | Number | 閲覧数 |

### ページネーションフッター

| 項目名 | 型 | 説明 |
|--------|-----|------|
| currentPage | Number | 現在のページ番号 |
| totalItems | Number | 総記事数 |
| itemsPerPage | Number | 1ページあたりの表示件数（25） |

## イベント仕様

### 1-画面初期表示

**トリガー**: コンポーネントのマウント時（onMounted）およびルートパラメータ変更時

**処理フロー**:
1. URLパラメータからportalSlug, locale, categorySlug, tabを取得
2. `articles/index` アクションをディスパッチして記事一覧を取得
3. 取得した記事をArticleListコンポーネントで表示

### 2-タブ切り替え

**トリガー**: タブクリック

**処理フロー**:
1. 選択されたタブの値を取得（all/mine/draft/archived）
2. tabパラメータを更新してルートを変更
3. watchによりfetchArticles関数が自動実行

**タブと対応するステータス**:
- All articles: status = null（全件）
- Mine: author_id = currentUserId
- Draft: status = 'draft'
- Archived: status = 'archived'

### 3-カテゴリ変更

**トリガー**: カテゴリセレクト変更

**処理フロー**:
1. 選択されたカテゴリスラッグを取得
2. categorySlugパラメータを更新
3. 記事一覧を再取得

### 4-ロケール変更

**トリガー**: ロケールセレクト変更

**処理フロー**:
1. 選択されたロケールコードを取得
2. localeパラメータを更新
3. categorySlugをリセット
4. `fetchPortal` イベントを発火してポータル情報を再取得
5. 記事一覧を再取得

### 5-ページ変更

**トリガー**: ページネーションコントロール操作

**処理フロー**:
1. 選択されたページ番号を取得
2. `pageChange` イベントを発火
3. fetchArticlesをpageNumber付きで実行

### 6-新規記事作成

**トリガー**: 「New article」ボタンクリック

**処理フロー**:
1. navigateToNewArticlePage関数を実行
2. 現在のcategorySlug, localeをパラメータとして設定
3. `portals_articles_new` ルートへ遷移

### 7-記事クリック

**トリガー**: 記事カードクリック

**処理フロー**:
1. ArticleListコンポーネントで記事IDを取得
2. `portals_articles_edit` ルートへ遷移

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

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

| 操作（イベント） | 対象テーブル | 操作種別 | 概要 |
|----------------|-------------|---------|------|
| 記事一覧取得 | articles | SELECT | フィルタ条件に基づく記事一覧取得 |
| カテゴリ取得 | categories | SELECT | ポータル・ロケールに紐づくカテゴリ一覧取得 |
| ポータル取得 | portals | SELECT | ポータル情報取得 |

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

#### articles

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | * | portal_id, locale, status, author_id, category_id | JOINでcategoriesテーブルも参照 |

## メッセージ仕様

| 種別 | メッセージキー | メッセージ内容 | 表示条件 |
|------|--------------|--------------|----------|
| タブラベル | HELP_CENTER.ARTICLES_PAGE.ARTICLES_HEADER.TABS.ALL | "All articles" | 常時 |
| タブラベル | HELP_CENTER.ARTICLES_PAGE.ARTICLES_HEADER.TABS.MINE | "Mine" | 常時 |
| タブラベル | HELP_CENTER.ARTICLES_PAGE.ARTICLES_HEADER.TABS.DRAFT | "Draft" | 常時 |
| タブラベル | HELP_CENTER.ARTICLES_PAGE.ARTICLES_HEADER.TABS.ARCHIVED | "Archived" | 常時 |
| 新規作成ボタン | HELP_CENTER.ARTICLES_PAGE.ARTICLES_HEADER.NEW_ARTICLE | "New article" | 常時 |
| 空状態タイトル（全記事） | HELP_CENTER.ARTICLES_PAGE.EMPTY_STATE.ALL.TITLE | "Write an article" | 記事なし時 |
| 空状態サブタイトル | HELP_CENTER.ARTICLES_PAGE.EMPTY_STATE.ALL.SUBTITLE | "Write a rich article..." | 記事なし時 |
| 空状態タイトル（自分の記事） | HELP_CENTER.ARTICLES_PAGE.EMPTY_STATE.MINE.TITLE | "You haven't written any articles here" | 自分の記事なし時 |
| 空状態タイトル（下書き） | HELP_CENTER.ARTICLES_PAGE.EMPTY_STATE.DRAFT.TITLE | "There are no articles in drafts" | 下書きなし時 |
| 空状態タイトル（アーカイブ） | HELP_CENTER.ARTICLES_PAGE.EMPTY_STATE.ARCHIVED.TITLE | "There are no articles in the archive" | アーカイブなし時 |

## 例外処理

| 例外状況 | 処理内容 |
|---------|---------|
| 記事取得失敗 | エラーがスローされ、空のリストを表示 |
| ポータルが見つからない | 空状態を表示 |
| 権限不足 | ルートガードによりアクセス拒否 |

## 備考

- 1ページあたり25件の記事を表示
- カテゴリフィルタ適用時は記事の順序はposition順
- カテゴリフィルタ未適用時は記事の順序はupdated_at降順
- watchでルートパラメータの変更を監視し、自動的にデータを再取得

---

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

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

### 推奨読解順序

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

記事とカテゴリのデータ構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | Article (Model) | `app/models/article.rb` | 記事のスキーマ定義、statusのenum、searchスコープ |
| 1-2 | Category (Model) | `app/models/category.rb` | カテゴリのスキーマ定義 |
| 1-3 | index.js (articles) | `app/javascript/dashboard/store/modules/helpCenterArticles/index.js` | Vuexストアの初期状態 |

**読解のコツ**: Article.searchメソッドの実装を確認し、どのようなフィルタが適用可能か理解する。

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

画面のエントリーポイントとコンポーネント構成を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | PortalsArticlesIndexPage.vue | `app/javascript/dashboard/routes/dashboard/helpcenter/pages/PortalsArticlesIndexPage.vue` | 画面コンポーネントのメイン処理 |
| 2-2 | ArticlesPage.vue | `app/javascript/dashboard/components-next/HelpCenter/Pages/ArticlePage/ArticlesPage.vue` | UI表示ロジック |
| 2-3 | helpcenter.routes.js | `app/javascript/dashboard/routes/dashboard/helpcenter/helpcenter.routes.js` | ルート定義 |

**主要処理フロー**:
1. **61-70行目**: fetchArticles関数で記事取得パラメータを構築
2. **87-98行目**: watchでルートパラメータ変更を監視
3. **76-85行目**: fetchPortalAndItsCategories関数でポータル・カテゴリ取得

#### Step 3: 子コンポーネントを理解する

記事一覧表示のUIコンポーネントを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | ArticlesPage.vue | `app/javascript/dashboard/components-next/HelpCenter/Pages/ArticlePage/ArticlesPage.vue` | ヘッダーコントロール、記事リスト、ページネーション |
| 3-2 | ArticleHeaderControls.vue | `app/javascript/dashboard/components-next/HelpCenter/Pages/ArticlePage/ArticleHeaderControls.vue` | タブ・フィルタUI |
| 3-3 | ArticleList.vue | `app/javascript/dashboard/components-next/HelpCenter/Pages/ArticlePage/ArticleList.vue` | 記事カードリスト |

**主要処理フロー**:
- **ArticlesPage.vue 112-116行目**: タブ変更時のルート更新
- **ArticlesPage.vue 124-130行目**: 新規記事作成への遷移

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

記事取得のAPI呼び出し処理を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | actions.js (articles) | `app/javascript/dashboard/store/modules/helpCenterArticles/actions.js` | indexアクションの実装 |
| 4-2 | articles.js (API) | `app/javascript/dashboard/api/helpCenter/articles.js` | getArticles APIメソッド |

**主要処理フロー**:
- **8-35行目**: indexアクション - パラメータ構築、API呼び出し、ストア更新

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

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

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

**主要処理フロー**:
- **7-18行目**: indexアクション - 検索条件適用、ソート、ページネーション

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

```
PortalsArticlesIndexPage.vue
    │
    ├─ onMounted() / watch()
    │      └─ fetchArticles()
    │             └─ store.dispatch('articles/index')
    │                    └─ actions.index()
    │                           └─ articlesAPI.getArticles()
    │                                  └─ GET /api/v1/accounts/:id/portals/:slug/articles
    │                                         └─ ArticlesController#index
    │
    ├─ fetchPortalAndItsCategories()
    │      ├─ store.dispatch('portals/index')
    │      ├─ store.dispatch('portals/show')
    │      ├─ store.dispatch('categories/index')
    │      └─ store.dispatch('agents/get')
    │
    └─ ArticlesPage.vue
           │
           ├─ ArticleHeaderControls.vue
           │      ├─ @tab-change → handleTabChange() → updateRoute()
           │      ├─ @locale-change → handleLocaleAction()
           │      ├─ @category-change → handleCategoryAction()
           │      └─ @new-article → navigateToNewArticlePage()
           │
           ├─ ArticleList.vue
           │      └─ ArticleCard[] → @click → portals_articles_edit
           │
           └─ HelpCenterLayout.vue
                  └─ @update:current-page → handlePageChange()
```

### データフロー図

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

URLパラメータ ────────▶ fetchArticles() ────────────▶ store.state.articles
  - portalSlug              │
  - locale                  ▼
  - categorySlug       articles/index action
  - tab                     │
                            ▼
                       ArticlesAPI.getArticles()
                            │
                            ▼
                       ArticlesController#index ────▶ articles[]
                            │                            │
                            │                            ▼
                            ├────────────────────────▶ Article.search()
                            │                            │
                            └────────────────────────▶ pagination
                                                         │
                                                         ▼
                                                    ArticleList
                                                         │
                                                         ▼
                                                    ArticleCard[]
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| PortalsArticlesIndexPage.vue | `app/javascript/dashboard/routes/dashboard/helpcenter/pages/PortalsArticlesIndexPage.vue` | ソース | 画面コンポーネント |
| ArticlesPage.vue | `app/javascript/dashboard/components-next/HelpCenter/Pages/ArticlePage/ArticlesPage.vue` | ソース | 記事一覧UI |
| ArticleHeaderControls.vue | `app/javascript/dashboard/components-next/HelpCenter/Pages/ArticlePage/ArticleHeaderControls.vue` | ソース | ヘッダーコントロール |
| ArticleList.vue | `app/javascript/dashboard/components-next/HelpCenter/Pages/ArticlePage/ArticleList.vue` | ソース | 記事リスト |
| HelpCenterLayout.vue | `app/javascript/dashboard/components-next/HelpCenter/HelpCenterLayout.vue` | ソース | レイアウト |
| helpcenter.routes.js | `app/javascript/dashboard/routes/dashboard/helpcenter/helpcenter.routes.js` | ソース | ルート定義 |
| actions.js | `app/javascript/dashboard/store/modules/helpCenterArticles/actions.js` | ソース | Vuexアクション |
| articles.js | `app/javascript/dashboard/api/helpCenter/articles.js` | ソース | APIクライアント |
| articles_controller.rb | `app/controllers/api/v1/accounts/articles_controller.rb` | ソース | APIコントローラー |
| article.rb | `app/models/article.rb` | ソース | モデル定義 |
| portalHelper.js | `app/javascript/dashboard/helper/portalHelper.js` | ソース | ヘルパー関数 |
