# 帳票設計書 9-エントリ一覧表示

## 概要

本ドキュメントは、Decap CMSにおけるコレクション内のエントリをカードグリッド形式で一覧表示する機能の設計を記述する。タイトル・説明・画像の推論フィールドを利用し、Waypoint（無限スクロール）に対応するEntryListingコンポーネントの仕組みを定義する。

### 本帳票の処理概要

本帳票は、CMSのコレクション画面で、コレクション内のエントリ群をカードグリッド形式の一覧として表示する機能を定義する。

**業務上の目的・背景**：コンテンツ編集者が管理する大量のエントリを効率的にブラウズし、目的のエントリを見つけるための一覧表示機能である。カードUIによる視覚的な一覧表示で、タイトル・画像・ワークフローステータスを一目で確認でき、エントリの選択・編集への導線を提供する。無限スクロール対応により、大量エントリの段階的読み込みを実現している。

**帳票の利用シーン**：CMSのサイドバーからコレクションを選択した際に、そのコレクションに属するエントリの一覧が表示される。検索結果の表示や、複数コレクションにまたがるエントリ表示にも使用される。エディトリアルワークフロー有効時は、未公開エントリも一覧に含まれ、ワークフローステータスバッジが表示される。

**主要な出力内容**：
1. エントリカード一覧（CardsGridレイアウト）
2. 各エントリカードのタイトル（推論フィールドまたは明示フィールド）
3. 各エントリカードの画像（推論フィールド、グリッド表示時のみ）
4. ワークフローステータスバッジ（Draft/In Review/Ready）
5. 無限スクロール用のWaypointコンポーネント

**帳票の出力タイミング**：コレクション画面表示時、およびスクロールによる追加データ読み込み時。

**帳票の利用者**：コンテンツ編集者（CMS利用者）

## 帳票種別

画面表示（一覧表示）

## 利用画面

| 画面No | 画面名 | URL/ルーティング | 出力操作 |
|--------|--------|-----------------|---------|
| - | コレクション一覧画面 | /collections/{collection} | コレクション選択時に自動表示 |
| - | 検索結果画面 | /search | 検索実行時に結果表示 |

## 出力形式

### 基本仕様

| 項目 | 内容 |
|-----|------|
| ファイル形式 | HTML（ブラウザ画面表示） |
| 用紙サイズ | N/A（ブラウザ画面内表示） |
| 向き | N/A |
| ファイル名 | N/A（画面表示） |
| 出力方法 | Reactコンポーネントによるレンダリング |
| 文字コード | UTF-8 |

### PDF固有設定

N/A

### Excel固有設定

N/A

## 帳票レイアウト

### レイアウト概要

CardsGridコンテナ内にエントリカードがフレックスラップで配置される。リスト表示とグリッド表示の2つのビュースタイルに対応する。

```
┌──────────────────────────────────────────────┐
│ CardsGrid (display: flex; flex-flow: row wrap)│
│                                               │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐      │
│ │EntryCard │ │EntryCard │ │EntryCard │      │
│ │ タイトル  │ │ タイトル  │ │ タイトル  │      │
│ │ [画像]   │ │ [画像]   │ │ [画像]   │      │
│ │ [Badge]  │ │ [Badge]  │ │ [Badge]  │      │
│ └──────────┘ └──────────┘ └──────────┘      │
│                                               │
│ ┌──────────┐ ┌──────────┐                    │
│ │EntryCard │ │EntryCard │                    │
│ │ タイトル  │ │ タイトル  │  [Waypoint]       │
│ │ [画像]   │ │ [画像]   │  (無限スクロール)  │
│ └──────────┘ └──────────┘                    │
└──────────────────────────────────────────────┘
```

### ヘッダー部

N/A（一覧表示のため個別ヘッダーなし）

### 明細部

| No | 項目名 | 説明 | データ取得元 | 表示形式 | 列幅 |
|----|-------|------|-------------|---------|-----|
| 1 | タイトル | エントリのタイトル | selectEntryCollectionTitle(collection, entry) | テキスト | N/A |
| 2 | 画像 | エントリの画像（グリッド表示時） | entry.data[inferredFields.imageField] | 背景画像（CardImage） | N/A |
| 3 | コレクションラベル | 複数コレクション表示時のラベル | collection.get('label') | 大文字テキスト | N/A |
| 4 | ワークフローステータス | エントリの公開状態バッジ | getWorkflowStatus(collection, slug) | WorkflowBadge | N/A |
| 5 | リンク | エントリ編集画面へのリンク | `/collections/{name}/entries/{slug}` | react-router Link | N/A |

### フッター部

| No | 項目名 | 説明 | データ取得元 | 表示形式 |
|----|-------|------|-------------|---------|
| 1 | Waypoint | 無限スクロール用のトリガー | cursor.actions.has('append_next') | Waypointコンポーネント |

## 出力条件

### 抽出条件

| 条件名 | 説明 | 必須 |
|-------|------|-----|
| コレクション | 表示対象のコレクション定義 | Yes |
| エントリデータ | Redux storeから取得されたエントリ一覧 | Yes |
| ワークフローステータス | エディトリアルワークフロー有効時の公開状態 | No |

### ソート順

| 優先度 | 項目 | 昇順/降順 |
|-------|------|---------|
| 1 | Redux storeのエントリ順序（バックエンドの返却順） | バックエンド依存 |

### 改ページ条件

無限スクロール方式。Waypointコンポーネントが画面に入った時点で`handleCursorActions('append_next')`を呼び出し、次ページのエントリを追加読み込みする。

## データベース参照仕様

### 参照テーブル一覧

N/A（Redux storeからデータ取得。バックエンドはGitリポジトリ）

### テーブル別参照項目詳細

N/A

## 計算仕様

### 計算項目一覧

| 項目名 | 計算式 | 端数処理 | 備考 |
|-------|-------|---------|------|
| フィールド推論 | `selectInferredField(collection, 'title'/'description'/'image')` | N/A | コレクション設定からtitle/description/imageフィールドを推論 |
| 残余フィールド | `fields.filter(f => inferredFields.indexOf(f.get('name')) === -1)` | N/A | 推論フィールド以外のフィールドを抽出 |
| 追加ページ有無 | `cursor?.actions?.has('append_next')` | N/A | カーソルにappend_nextアクションがあるか判定 |
| 未公開エントリ統合 | `entries.concat(uniqueUnpublished)` | N/A | 公開済みと未公開のエントリを重複なしで結合 |
| 画像URL | `encodeURI(image)` | N/A | 画像パスをURI形式でエンコード |

## 処理フロー

### 出力フロー

```mermaid
flowchart TD
    A[コレクション画面表示] --> B[EntryListing render]
    B --> C{単一コレクション?}
    C -->|Yes| D[renderCardsForSingleCollection]
    C -->|No| E[renderCardsForMultipleCollections]
    D --> F[getAllEntries - 未公開含む]
    F --> G[inferFields - フィールド推論]
    G --> H[entries.map - EntryCard生成]
    E --> I[entries.map - コレクション別EntryCard生成]
    H --> J[CardsGrid レンダリング]
    I --> J
    J --> K{hasMore?}
    K -->|Yes| L[Waypoint配置]
    K -->|No| M[表示完了]
    L --> N[スクロールでWaypoint到達]
    N --> O[handleLoadMore - 追加読み込み]
    O --> B
```

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 表示メッセージ | 対処方法 |
|----------|---------|--------------|---------|
| データなし | entriesが空の場合 | 空の一覧表示（カードなし） | エントリを作成する |
| 読み込みエラー | バックエンドからのデータ取得失敗 | 上位コンポーネント（EntriesCollection）で処理 | ネットワーク接続を確認 |

## パフォーマンス要件

| 項目 | 内容 |
|-----|------|
| 想定データ件数 | 数百〜数千エントリ（無限スクロールで段階読み込み） |
| 目標出力時間 | 初回表示: 数百ミリ秒、追加読み込み: 数百ミリ秒 |
| 同時出力数上限 | 1ページあたりのエントリ数はバックエンド設定に依存 |

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

- エントリカードのリンクはreact-routerのLinkコンポーネントを使用し、XSSリスクを軽減
- 画像URLはencodeURIで処理されるが、CSS background-imageとして使用されるため、信頼されたソースの画像のみを想定

## 備考

- EntryListingはReact ClassコンポーネントでImmutable.jsのMap/Listを使用
- CardsGridはCSS Flexboxでrow wrap配置
- 単一コレクション表示と複数コレクション表示で異なるレンダリングロジック
- 未公開エントリの統合は、公開済みエントリのslugセットを構築し、重複を除外して結合する
- ネストされたコレクションの場合、filterNestedEntries関数でサブフォルダ内のエントリをフィルタリング
- viewStyleプロパティでリスト表示（VIEW_STYLE_LIST）とグリッド表示（VIEW_STYLE_GRID）を切り替え可能

---

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

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

### 推奨読解順序

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

エントリ一覧に関わるデータ構造とpropsを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | EntryListing.js | `packages/decap-cms-core/src/components/Collection/Entries/EntryListing.js` | static propTypes（22-32行目）で受け取るpropsの構造を確認。collections, entries, viewStyle, cursor, handleCursorActions, page, getUnpublishedEntries, getWorkflowStatus, filterTermを理解 |

**読解のコツ**: EntryListingはpropsでcollectionsを受け取るが、単一コレクション（Map）と複数コレクション（Iterable）の両方を扱う。Map.isMap(collections)で判定する。

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

renderメソッドの処理フローを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | EntryListing.js | `packages/decap-cms-core/src/components/Collection/Entries/EntryListing.js` | renderメソッド（135-148行目）で単一/複数コレクションの分岐とWaypoint配置を確認 |

**主要処理フロー**:
1. **141行目**: `Map.isMap(collections)` で単一/複数コレクションを判定
2. **142行目**: 単一コレクション時は`renderCardsForSingleCollection()`
3. **143行目**: 複数コレクション時は`renderCardsForMultipleCollections()`
4. **144行目**: `this.hasMore()` でページネーションの有無を判定
5. **144行目**: hasMoreの場合、`Waypoint`コンポーネントを配置

#### Step 3: 単一コレクションのカード生成を理解する

renderCardsForSingleCollectionの処理を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | EntryListing.js | `packages/decap-cms-core/src/components/Collection/Entries/EntryListing.js` | renderCardsForSingleCollection（97-113行目）でgetAllEntries、inferFields、EntryCard生成の流れを確認 |
| 3-2 | EntryListing.js | `packages/decap-cms-core/src/components/Collection/Entries/EntryListing.js` | getAllEntries（61-95行目）で未公開エントリを統合する処理を確認 |

**主要処理フロー**:
- **99行目**: `this.getAllEntries()` で公開済み + 未公開エントリを取得
- **100行目**: `this.inferFields(collections)` でtitle/description/imageフィールドを推論
- **103行目**: `allEntries.map()` で各エントリのEntryCardを生成
- **104-107行目**: `getWorkflowStatus(collections.get('name'), entry.get('slug'))` でワークフローステータスを取得
- **63-64行目**: getAllEntriesでcollectionNameが存在するか確認
- **69行目**: `getUnpublishedEntries(collectionName)` で未公開エントリを取得
- **89-90行目**: publishedSlugsセットを構築し、重複除外で未公開エントリを結合

#### Step 4: EntryCardコンポーネントを理解する

各エントリカードの表示ロジックを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | EntryCard.js | `packages/decap-cms-core/src/components/Collection/Entries/EntryCard.js` | EntryCard関数コンポーネント（134-200行目）でviewStyleに応じたリスト/グリッド表示の切り替えを確認 |
| 4-2 | EntryCard.js | `packages/decap-cms-core/src/components/Collection/Entries/EntryCard.js` | mapStateToProps（202-223行目）でsummary、path、image、imageFieldの算出ロジックを確認 |

**主要処理フロー**:
- **158行目**: `viewStyle === VIEW_STYLE_LIST` でリスト表示
- **178行目**: `viewStyle === VIEW_STYLE_GRID` でグリッド表示
- **205行目**: `selectEntryCollectionTitle(collection, entry)` でタイトルを取得
- **207-209行目**: imageフィールドの値を取得し、encodeURIでエンコード
- **215行目**: `path: /collections/${name}/entries/${slug}` でリンクURLを生成

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

```
EntriesCollection (EntriesCollection.js)
    |
    +-- Entries
            |
            +-- EntryListing (EntryListing.js)
                    |
                    +-- renderCardsForSingleCollection (97行目)
                    |       +-- getAllEntries (61行目)
                    |       |       +-- getUnpublishedEntries (props)
                    |       +-- inferFields (50行目)
                    |       |       +-- selectInferredField (reducers/collections)
                    |       +-- EntryCard (EntryCard.js)
                    |               +-- selectEntryCollectionTitle
                    |               +-- getAsset (画像取得)
                    |
                    +-- renderCardsForMultipleCollections (115行目)
                    |       +-- EntryCard
                    |
                    +-- Waypoint (react-waypoint)
                            +-- handleLoadMore (44行目)
                                    +-- handleCursorActions('append_next')
```

### データフロー図

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

Redux Store ────────────┐
  |                     |
  +-- entries ----------+-> EntryListing ---------> CardsGrid
  +-- collections ------+     |                      |
  +-- cursor -----------+     +-- getAllEntries       +-- EntryCard x N
  +-- page -------------+     |     +-- concat        |     +-- summary
                              |         unpublished   |     +-- image
  props ────────────────+     +-- inferFields         |     +-- path
  +-- viewStyle --------+     +-- EntryCard           |     +-- WorkflowBadge
  +-- filterTerm -------+     +-- Waypoint            |
                                                      +-- Waypoint
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| EntryListing.js | `packages/decap-cms-core/src/components/Collection/Entries/EntryListing.js` | ソース | エントリ一覧メインコンポーネント |
| EntryCard.js | `packages/decap-cms-core/src/components/Collection/Entries/EntryCard.js` | ソース | 個別エントリカードコンポーネント（リスト/グリッド表示） |
| EntriesCollection.js | `packages/decap-cms-core/src/components/Collection/Entries/EntriesCollection.js` | ソース | コレクションエントリの読み込み・フィルタリング・Redux接続 |
| collections.js | `packages/decap-cms-core/src/reducers/collections.js` | ソース | selectInferredField、selectEntryCollectionTitle等のセレクタ |
