# 画面設計書 26-バリアントメディア編集画面

## 概要

本ドキュメントは、Medusa Admin Dashboard における「バリアントメディア編集画面」の設計仕様を定義するものである。

### 本画面の処理概要

バリアントメディア編集画面は、商品バリアントに紐付ける画像を管理するためのフルスクリーンモーダル形式の画面である。商品に登録されている画像の中から、バリアントに関連付ける画像を選択し、サムネイル画像の設定も行える。

**業務上の目的・背景**：ECサイトでは、同一商品でも色違いなどのバリエーションごとに異なる画像を表示することが重要である。この画面により、バリアント固有の画像を柔軟に設定でき、顧客に対して正確な商品イメージを提供できる。また、バリアントのサムネイル画像を設定することで、カート画面や注文画面での視認性を向上させる。

**画面へのアクセス方法**：バリアント詳細画面のメディアセクションにあるアクションメニューから「メディアを編集」を選択することでモーダル形式で表示される。URLは `/products/:id/variants/:variant_id/media` の形式となる。

**主要な操作・処理内容**：
1. 現在バリアントに紐付いている画像の表示
2. 商品の未使用画像からの追加
3. 画像の選択・複数選択
4. サムネイル画像への昇格
5. 選択画像のバリアントからの削除
6. 変更内容の保存

**画面遷移**：バリアント詳細画面からモーダルとして開き、保存またはキャンセル後にバリアント詳細画面に戻る。

**権限による表示制御**：商品の編集権限を持つユーザーのみが操作可能。

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 8 | 商品画像管理 | 主機能 | バリアントのメディア編集 |

## 画面種別

編集（フルスクリーンモーダル形式）

## URL/ルーティング

`/products/:id/variants/:variant_id/media`

## 入出力項目

| 項目名 | 項目ID | データ型 | 必須 | 入力/出力 | 説明 |
|--------|--------|----------|------|-----------|------|
| 商品ID | id | string | 必須 | 入力（URLパラメータ） | 親商品のID |
| バリアントID | variant_id | string | 必須 | 入力（URLパラメータ） | 対象バリアントのID |
| 画像ID配列 | image_ids | string[] | - | 入出力 | バリアントに紐付ける画像IDの配列 |
| サムネイルURL | thumbnail | string | - | 入出力 | サムネイル画像のURL |

## 表示項目

| 項目名 | データ型 | 説明 |
|--------|----------|------|
| メイン領域 | grid | バリアントに紐付いた画像のグリッド表示 |
| サイドバー | panel | 利用可能な（未紐付けの）画像一覧 |
| 画像カード | card | 各画像のサムネイル表示 |
| チェックボックス | checkbox | 画像の選択状態 |
| サムネイルバッジ | badge | サムネイル画像を示すアイコン |
| コマンドバー | CommandBar | 選択時のアクションバー |

## イベント仕様

### 1-画像チェックボックス選択

メイン領域の画像をクリックまたはチェックボックスをクリックすると、選択状態が切り替わる。複数選択可能。

### 2-サイドバー画像クリック

サイドバーの利用可能な画像をクリックすると、その画像がバリアントに追加される（フォームの image_ids に追加）。

### 3-サムネイルに設定（コマンドバー）

1つの画像を選択した状態でコマンドバーの「サムネイルにする」を選択またはショートカットキー「t」を押下すると、その画像がサムネイルに設定される。

### 4-選択画像を削除（コマンドバー）

画像を選択した状態でコマンドバーの「削除」を選択またはショートカットキー「r」を押下すると、選択された画像がバリアントから削除される。

### 5-モバイルサイドバー表示切り替え

モバイル表示時は「利用可能な画像を表示」ボタンでサイドバーをオーバーレイ表示。

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

現在の設定と初期状態を比較し、追加・削除を判定してAPIを呼び出す。サムネイル変更がある場合は別途バリアント更新も実行。

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

変更を破棄し、モーダルを閉じてバリアント詳細画面に戻る。

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

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

| 操作（イベント） | 対象テーブル | 操作種別 | 概要 |
|----------------|-------------|---------|------|
| 保存ボタン押下 | product_image_variant（中間テーブル） | INSERT/DELETE | 画像とバリアントの紐付けを更新 |
| 保存ボタン押下（サムネイル変更時） | product_variant | UPDATE | サムネイルURLを更新 |

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

#### product_image_variant（画像-バリアント中間テーブル）

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | image_id, variant_id | 新規追加された画像との紐付け | addパラメータで指定 |
| DELETE | image_id, variant_id | 削除された画像との紐付け | removeパラメータで指定 |

#### product_variant

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| UPDATE | thumbnail | 選択した画像のURL | サムネイル変更時のみ |

## メッセージ仕様

| メッセージID | 種別 | メッセージ内容 | 表示条件 |
|-------------|------|---------------|----------|
| products.media.successToast | 成功 | メディアが正常に更新されました | 保存成功時 |
| - | エラー | {エラーメッセージ} | API呼び出し失敗時 |
| products.media.thumbnailTooltip | ツールチップ | サムネイル画像 | サムネイルバッジにホバー時 |
| products.media.makeThumbnail | ラベル | サムネイルにする | コマンドバー |
| products.media.removeSelected | ラベル | 選択を削除 | コマンドバー |
| products.media.availableImages | ラベル | 利用可能な画像 | サイドバーヘッダー |
| products.media.selectToAdd | 説明 | クリックして追加 | サイドバー説明 |

## 例外処理

| 例外条件 | 処理内容 |
|----------|----------|
| バリアントが見つからない | エラーをthrowし、エラーバウンダリで処理 |
| API呼び出し失敗 | トースト通知でエラーメッセージを表示 |

## 備考

- useBatchVariantImagesフックで画像の追加・削除をバッチ実行
- useUpdateProductVariantフックでサムネイル更新
- CommandBarで選択時のコンテキストアクションを提供
- レスポンシブ対応：デスクトップではサイドバー固定、モバイルではオーバーレイ
- blockSearchParams=trueでURLパラメータの影響を制御
- 既にバリアントのサムネイルに設定されている画像は「サムネイルにする」アクションが非表示

---

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

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

### 推奨読解順序

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

フォームスキーマと画像データの構造を理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | edit-product-variant-media-form.tsx | `packages/admin/dashboard/src/routes/product-variants/product-variant-media/components/edit-product-variant-media-form/edit-product-variant-media-form.tsx` | MediaSchema（23-26行目）でフォーム構造を確認 |
| 1-2 | edit-product-variant-media-form.tsx | `packages/admin/dashboard/src/routes/product-variants/product-variant-media/components/edit-product-variant-media-form/edit-product-variant-media-form.tsx` | ProductVariantMediaViewProps（33-37行目）でpropsの型を確認 |

**読解のコツ**: image_idsは文字列配列、thumbnailはnullable文字列。variant.imagesとvariant.product.imagesの両方を使用。

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

ルートコンポーネントの処理を追う。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | product-variant-media.tsx | `packages/admin/dashboard/src/routes/product-variants/product-variant-media/product-variant-media.tsx` | useParamsでパラメータ取得（13行目） |
| 2-2 | product-variant-media.tsx | `packages/admin/dashboard/src/routes/product-variants/product-variant-media/product-variant-media.tsx` | useProductVariantでデータ取得（15-21行目） |
| 2-3 | product-variant-media.tsx | `packages/admin/dashboard/src/routes/product-variants/product-variant-media/product-variant-media.tsx` | RouteFocusModalでフルスクリーンモーダル（30行目） |

**主要処理フロー**:
1. **13行目**: useParamsでURLパラメータを取得
2. **15-21行目**: useProductVariantで画像情報を含むバリアントデータを取得
3. **30-36行目**: RouteFocusModalでラップし、データ取得完了後にフォームを描画

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

メディア編集フォームの実装詳細を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | edit-product-variant-media-form.tsx | `packages/admin/dashboard/src/routes/product-variants/product-variant-media/components/edit-product-variant-media-form/edit-product-variant-media-form.tsx` | 画像の分類処理（45-52行目）allProductImages/allVariantImages/unassociatedImages |
| 3-2 | edit-product-variant-media-form.tsx | `packages/admin/dashboard/src/routes/product-variants/product-variant-media/components/edit-product-variant-media-form/edit-product-variant-media-form.tsx` | handleSubmit（80-121行目）で追加・削除・サムネイル更新の処理 |
| 3-3 | edit-product-variant-media-form.tsx | `packages/admin/dashboard/src/routes/product-variants/product-variant-media/components/edit-product-variant-media-form/edit-product-variant-media-form.tsx` | CommandBar（297-322行目）で選択時アクションの実装 |

**主要処理フロー**:
- **45-52行目**: 全商品画像、バリアント画像、未使用画像を分類
- **57-63行目**: useFormでフォーム初期化
- **80-121行目**: handleSubmitで差分を計算し、useBatchVariantImagesとuseUpdateProductVariantを呼び出し
- **146-159行目**: handlePromoteToThumbnailでサムネイル設定
- **162-178行目**: handleRemoveSelectedImagesで画像削除

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

```
ProductVariantMedia (product-variant-media.tsx:12-38)
    │
    ├─ useParams() → URLパラメータ取得
    │
    ├─ useProductVariant(id, variant_id) → バリアント・画像データ取得
    │      └─ sdk.admin.productVariant.retrieve()
    │
    └─ EditProductVariantMediaForm (edit-product-variant-media-form.tsx:39-337)
           │
           ├─ useForm() → フォーム状態管理
           │
           ├─ useState(selection) → 選択状態管理
           │
           ├─ useUpdateProductVariant() → サムネイル更新
           │      └─ sdk.admin.productVariant.update()
           │
           ├─ useBatchVariantImages() → 画像バッチ更新
           │      └─ sdk.admin.productVariant.batchImages()
           │
           ├─ MediaGridItem × N → 画像表示・選択
           │
           ├─ UnassociatedImageItem × N → 未使用画像表示
           │
           └─ CommandBar → 選択時アクション
```

### データフロー図

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

URLパラメータ(id, variant_id) ───▶ useProductVariant ───▶ バリアント・画像データ取得
                                          │
                                          ▼
                              ┌─────────────────────┐
                              │ 画像の分類処理        │
                              │ ・allProductImages   │
                              │ ・allVariantImages   │
                              │ ・unassociatedImages │
                              └─────────────────────┘
                                          │
                              ┌───────────┴───────────┐
                              ▼                       ▼
                        メイン領域               サイドバー
                        (選択可能)              (追加可能)
                              │                       │
画像選択/チェック ─────────────▶ selection更新 ◀────────── 画像クリック追加
                              │
                              ▼
                         CommandBar表示
                              │
サムネイル設定(t) ───────────▶ thumbnail更新
削除(r) ─────────────────────▶ image_ids更新
                              │
保存ボタン押下 ───────────────▶ handleSubmit
                              │
                        ┌─────┴─────┐
                        ▼           ▼
              画像バッチ更新    サムネイル更新
                        │           │
                        └─────┬─────┘
                              ▼
                        handleSuccess() ───▶ モーダルを閉じる
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| product-variant-media.tsx | `packages/admin/dashboard/src/routes/product-variants/product-variant-media/product-variant-media.tsx` | ソース | 画面のエントリーポイント |
| edit-product-variant-media-form.tsx | `packages/admin/dashboard/src/routes/product-variants/product-variant-media/components/edit-product-variant-media-form/edit-product-variant-media-form.tsx` | ソース | メディア編集フォームコンポーネント |
| useProductVariant | `packages/admin/dashboard/src/hooks/api/products.ts` | ソース | バリアント取得フック |
| useBatchVariantImages | `packages/admin/dashboard/src/hooks/api/products.ts` | ソース | 画像バッチ更新フック |
| useUpdateProductVariant | `packages/admin/dashboard/src/hooks/api/products.ts` | ソース | バリアント更新フック |
| RouteFocusModal | `packages/admin/dashboard/src/components/modals/` | ソース | フルスクリーンモーダルコンポーネント |
| CommandBar | `@medusajs/ui` | 外部 | コンテキストアクションバー |
