# 画面設計書 39-コレクション商品追加画面

## 概要

本ドキュメントは、Medusa Admin Dashboard におけるコレクション商品追加画面の設計を記述したものです。

### 本画面の処理概要

商品コレクションに商品を追加するための画面です。商品一覧から追加したい商品を選択し、コレクションに紐付けます。

**業務上の目的・背景**：ECサイト運営において、コレクションに関連する商品を追加していく必要があります。季節商品の追加、新商品の組み入れ、キャンペーン対象商品の設定など、様々なシーンでコレクションへの商品追加が発生します。この画面では、商品一覧から複数の商品を選択して一括でコレクションに追加できます。

**画面へのアクセス方法**：コレクション詳細画面の商品セクションから「追加」ボタン押下で、フルスクリーンモーダル形式で表示されます。

**主要な操作・処理内容**：
1. 商品一覧の表示 - 全商品をテーブル形式で表示（50件/ページ）
2. 商品の検索・フィルタリング - タイトル、作成日などでフィルタ
3. 商品の選択 - チェックボックスで複数商品を選択
4. 保存ボタン押下で選択商品をコレクションに追加

**画面遷移**：コレクション詳細画面から遷移してきます。保存完了後またはキャンセル時にコレクション詳細画面に戻ります。

**権限による表示制御**：管理者権限を持つユーザーのみがコレクションに商品を追加できます。認証されていないユーザーはログイン画面にリダイレクトされます。

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 5 | 商品コレクション管理 | 主機能 | コレクションへの商品追加 |
| 1 | 商品CRUD | 補助機能 | 商品一覧の表示 |

## 画面種別

作成（フルスクリーンモーダル形式）

## URL/ルーティング

`/collections/:id/products`

## 入出力項目

| 項目名 | 項目ID | 型 | 必須 | 最大長 | 入力/出力 | 説明 |
|--------|--------|-----|------|--------|-----------|------|
| コレクションID | id | string | - | - | 入力(URL) | 追加先のコレクションID |
| 追加商品ID | add | string[] | 必須 | - | 入力 | 追加する商品IDの配列（最低1件） |

## 表示項目

| 項目名 | 表示形式 | 説明 |
|--------|----------|------|
| 商品テーブル | DataTable | 全商品一覧（50件/ページ） |
| 商品サムネイル | 画像 | 商品画像のサムネイル |
| 商品タイトル | Text | 商品名 |
| 商品ステータス | StatusBadge | 公開状態 |
| バリアント | Text | バリアント情報 |
| 販売チャネル | Text | 販売チャネル情報 |
| 検索ボックス | 検索入力 | 商品検索（autofocus） |
| フィルター | FilterMenu | コレクション以外のフィルター |
| ソート | OrderBy | タイトル、作成日、更新日でソート |
| バリデーションエラー | Hint | 商品未選択時のエラー表示 |
| キャンセルボタン | Button | "Cancel"（i18n: actions.cancel） |
| 保存ボタン | Button | "Save"（i18n: actions.save） |

## イベント仕様

### 1-保存ボタン押下

フォームのバリデーションを実行し、成功した場合はAPIを呼び出して商品をコレクションに追加します。

**処理フロー**:
1. Zodスキーマによるバリデーション実行（最低1件の商品選択が必須）
2. `useUpdateCollectionProducts` フックを使用してAPI呼び出し
3. 成功時: トースト通知（collections.products.add.successToast）を表示し、モーダルを閉じる
4. 失敗時: エラーメッセージをトーストで表示

**API呼び出し**:
- エンドポイント: `POST /admin/collections/:id/products`
- リクエストボディ:
  ```json
  {
    "add": ["prod_xxx", "prod_yyy"]
  }
  ```

### 2-キャンセルボタン押下

フォームの選択を破棄し、モーダルを閉じてコレクション詳細画面に戻ります。

### 3-商品選択

チェックボックスをクリックすると、選択状態がrowSelectionステートに反映され、フォームの値（add配列）も更新されます。

**処理フロー**:
1. チェックボックスのonCheckedChangeでrow.toggleSelected呼び出し
2. updater関数でrowSelectionステート更新
3. setValue("add", ...)でフォーム値を同期
4. 既にコレクションに追加済みの商品はチェックボックスがdisabledで表示

### 4-既存商品の表示

コレクションに既に追加されている商品は、チェックボックスがdisabledとなりTooltipで「既に追加済み」と表示されます。

### 5-ページネーション

商品一覧は50件/ページで表示され、ページ切り替えが可能です。

### 6-検索・フィルタリング

検索ボックスで商品タイトルを検索できます。フィルターではコレクション以外の条件（タイプ、ステータスなど）でフィルタリングできます。

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

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

| 操作（イベント） | 対象テーブル | 操作種別 | 概要 |
|----------------|-------------|---------|------|
| 保存ボタン押下 | product_collection_product | INSERT | コレクションと商品の紐付けを追加 |

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

#### product_collection_product（中間テーブル）

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | collection_id | URLパラメータのID | コレクションID |
| INSERT | product_id | 選択された商品ID | 各商品について1レコード |

## メッセージ仕様

| メッセージID | 種別 | メッセージ内容 | 表示タイミング |
|------------|------|--------------|--------------|
| collections.products.add.successToast | 成功 | {count}件の商品を追加しました | 追加成功時 |
| - | エラー | {error.message} | API呼び出し失敗時 |
| - | バリデーションエラー | 商品を選択してください | 商品未選択時 |
| salesChannels.productAlreadyAdded | 情報 | この商品は既に追加されています | 既存商品Tooltip |

## 例外処理

| 例外状況 | 処理内容 |
|---------|---------|
| コレクションが見つからない | エラーをスローし、エラーバウンダリで処理 |
| 商品一覧取得エラー | エラーをスローし、エラーバウンダリで処理 |
| API通信エラー | トーストでエラーメッセージを表示 |
| バリデーションエラー | ヘッダー部にHintでエラーメッセージを表示 |
| 認証エラー | ログイン画面にリダイレクト |

## 備考

- フルスクリーンモーダル形式（RouteFocusModal）で表示
- キーボードショートカット対応（KeyboundForm）
- フォームの状態管理にはreact-hook-formを使用
- バリデーションにはZodスキーマを使用（add配列は最低1件必須）
- 商品テーブルは50件/ページ（PAGE_SIZE = 50）
- クエリパラメータにはプレフィックス"p"を使用
- keepPreviousDataでプレースホルダー表示
- 検索ボックスにautofocusを設定
- コレクション以外のフィルターを表示（useProductTableFilters(["collections"])）
- 既存商品はcollection_id === collectionIdで判定
- 成功トーストに追加件数を表示

---

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

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

### 推奨読解順序

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

フォームのスキーマを理解することが重要です。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | add-products-to-collection-form.tsx | `packages/admin/dashboard/src/routes/collections/collection-add-products/components/add-products-to-collection-form/add-products-to-collection-form.tsx` | AddProductsToCollectionSchema（31-33行目）でバリデーションルールを確認 |

**読解のコツ**: addフィールドは`z.array(z.string()).min(1)`で最低1件の商品選択が必須です。

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

ルートコンポーネントから画面がどのように構成されるかを理解します。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | collection-add-products.tsx | `packages/admin/dashboard/src/routes/collections/collection-add-products/collection-add-products.tsx` | useParamsでID取得、useCollectionでデータフェッチ、RouteFocusModalでフルスクリーン表示 |

**主要処理フロー**:
1. **8行目**: useParamsでURLからコレクションIDを取得
2. **9行目**: useCollectionでコレクションデータをフェッチ
3. **11-13行目**: エラー時の例外スロー
4. **15-21行目**: RouteFocusModalでフルスクリーン表示、ローディング完了後にフォーム表示

#### Step 3: フォームコンポーネントを理解する

商品追加フォームの実装詳細を把握します。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | add-products-to-collection-form.tsx | `packages/admin/dashboard/src/routes/collections/collection-add-products/components/add-products-to-collection-form/add-products-to-collection-form.tsx` | useForm、useUpdateCollectionProducts、rowSelection管理を確認 |

**主要処理フロー**:
- **44-49行目**: useFormでフォーム初期化、add配列を空で初期化
- **53-55行目**: useUpdateCollectionProductsでミューテーションフック取得
- **57行目**: rowSelectionステートで選択状態を管理
- **59-75行目**: updater関数でrowSelectionとフォーム値を同期
- **77-86行目**: useEffectでrowSelection変更時にフォーム値を更新
- **88-101行目**: useProductsで全商品を取得（50件/ページ）
- **106-122行目**: useDataTableでテーブル設定
- **124-143行目**: handleSubmitで送信処理を定義

#### Step 4: テーブルカラム定義を理解する

商品テーブルのカラム定義と選択制御を把握します。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | add-products-to-collection-form.tsx | `packages/admin/dashboard/src/routes/collections/collection-add-products/components/add-products-to-collection-form/add-products-to-collection-form.tsx` | useColumns（199-259行目）で選択カラムの実装を確認 |

**主要処理フロー**:
- **205-254行目**: selectカラムで選択チェックボックスを表示
- **226-228行目**: collection_id === collectionIdで既存商品を判定
- **230-238行目**: 既存商品はdisabledで選択不可
- **241-249行目**: 既存商品にはTooltipで説明を表示

#### Step 5: APIフックを理解する

バックエンドとの通信処理を把握します。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | collections.tsx | `packages/admin/dashboard/src/hooks/api/collections.tsx` | useUpdateCollectionProductsの実装を確認 |

**主要処理フロー**:
- SDK経由でPOST /admin/collections/:id/productsを呼び出し
- 成功時にクエリキャッシュを無効化

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

```
CollectionAddProducts (collection-add-products.tsx)
    │
    ├─ useParams() → コレクションID取得
    │
    ├─ useCollection(id) → コレクションデータ取得
    │      └─ sdk.admin.productCollection.retrieve()
    │
    └─ RouteFocusModal
           │
           └─ AddProductsToCollectionForm (add-products-to-collection-form.tsx)
                  │
                  ├─ useForm() → フォーム状態管理
                  │
                  ├─ useRouteModal() → モーダル制御
                  │      └─ handleSuccess() → モーダルクローズ
                  │
                  ├─ useUpdateCollectionProducts(id) → 更新ミューテーション
                  │      └─ sdk.admin.productCollection.updateProducts()
                  │
                  ├─ useProductTableQuery() → クエリパラメータ管理
                  │
                  ├─ useProducts() → 商品一覧取得
                  │      └─ sdk.admin.product.list()
                  │
                  ├─ useDataTable() → テーブル制御
                  │
                  ├─ useColumns() → カラム定義
                  │      └─ useProductTableColumns()
                  │
                  ├─ useProductTableFilters() → フィルター定義
                  │
                  └─ _DataTable → 商品一覧表示
                         └─ RowSelection → 商品選択
```

### データフロー図

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

URLパラメータ(:id) ───▶ useCollection ───▶ コレクションデータ
                              │
                              ▼
検索・フィルター ────────▶ useProducts ───▶ 商品一覧表示
                        (keepPreviousData)
                              │
                              ▼
チェックボックス選択 ───▶ rowSelection更新
                              │
                              ▼
                        updater関数 ───▶ setValue("add", ...)
                              │
                              ▼
保存ボタン押下 ─────────▶ handleSubmit
                              │
                              ▼
                        Zodバリデーション
                        (add.min(1))
                              │
                              ▼
                        useUpdateCollectionProducts
                              │
                              ▼
                        SDK API呼び出し ───▶ 商品紐付け追加
                              │
                              ▼
                        toast.success() ───▶ 成功通知
                              │
                              ▼
                        handleSuccess() ───▶ モーダルクローズ
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| collection-add-products.tsx | `packages/admin/dashboard/src/routes/collections/collection-add-products/collection-add-products.tsx` | ソース | ルートコンポーネント |
| add-products-to-collection-form.tsx | `packages/admin/dashboard/src/routes/collections/collection-add-products/components/add-products-to-collection-form/add-products-to-collection-form.tsx` | ソース | 商品追加フォーム |
| index.ts | `packages/admin/dashboard/src/routes/collections/collection-add-products/index.ts` | ソース | エクスポート定義 |
| collections.tsx | `packages/admin/dashboard/src/hooks/api/collections.tsx` | ソース | コレクションAPI フック |
| products.tsx | `packages/admin/dashboard/src/hooks/api/products.tsx` | ソース | 商品API フック |
| use-product-table-columns.tsx | `packages/admin/dashboard/src/hooks/table/columns/use-product-table-columns.tsx` | ソース | 商品テーブルカラム定義 |
| use-product-table-filters.tsx | `packages/admin/dashboard/src/hooks/table/filters/use-product-table-filters.tsx` | ソース | 商品テーブルフィルター定義 |
| use-product-table-query.tsx | `packages/admin/dashboard/src/hooks/table/query/use-product-table-query.tsx` | ソース | 商品テーブルクエリ管理 |
| RouteFocusModal | `packages/admin/dashboard/src/components/modals/` | ソース | フルスクリーンモーダル |
| _DataTable | `packages/admin/dashboard/src/components/table/data-table/` | ソース | データテーブル |
| KeyboundForm | `packages/admin/dashboard/src/components/utilities/keybound-form/` | ソース | キーバインド対応フォーム |
