# 画面設計書 11-商品メディア編集画面

## 概要

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

### 本画面の処理概要

商品メディア編集画面は、商品に紐付く画像・動画等のメディアファイルを管理するための画面である。管理者は本画面を通じて、商品のビジュアル表現を最適化し、ECサイトでの商品訴求力を向上させることができる。

**業務上の目的・背景**：ECサイトにおいて、商品画像は購買決定に大きな影響を与える重要な要素である。本画面は、商品ごとに複数のメディアファイルを効率的に管理し、サムネイル設定や表示順序の調整を通じて、最適な商品プレゼンテーションを実現するために必要である。

**画面へのアクセス方法**：商品詳細画面のメディアセクションから「Edit」ボタンをクリックすることでモーダル形式で表示される。URLパスは `/products/:id/media` となる。

**主要な操作・処理内容**：
1. 新規メディアファイルのアップロード（ドラッグ&ドロップ対応）
2. 既存メディアの表示順序変更（ドラッグ&ドロップによるソート）
3. サムネイル画像の選択・変更
4. 不要なメディアの削除（複数選択による一括削除対応）
5. ギャラリービューとエディットビューの切り替え

**画面遷移**：
- 遷移元：商品詳細画面（No.7）
- 遷移先：商品詳細画面（保存/キャンセル後）

**権限による表示制御**：本画面へのアクセスには商品管理権限が必要。権限がない場合は画面にアクセスできない。

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 8 | 商品画像管理 | 主機能 | 商品画像の追加・削除・並び替え |
| 74 | ファイルアップロード | 補助機能 | 画像ファイルのアップロード処理 |

## 画面種別

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

## URL/ルーティング

```
/products/:id/media
```

パラメータ：
- `id`：商品ID（必須）

クエリパラメータ：
- `view`：表示モード（`gallery` または `edit`、デフォルトは `gallery`）

## 入出力項目

### 入力項目

| 項目名 | 項目ID | 型 | 必須 | バリデーション | 備考 |
|--------|--------|-----|------|---------------|------|
| メディアファイル | media | File[] | - | 画像/動画形式 | 複数ファイル選択可 |
| サムネイル指定 | isThumbnail | boolean | - | - | 1件のみtrue可 |
| 表示順序 | - | - | - | - | ドラッグ&ドロップで変更 |

### 出力項目

| 項目名 | 項目ID | 型 | 備考 |
|--------|--------|-----|------|
| メディア一覧 | media | Media[] | 画像URL、ID、サムネイルフラグを含む |

## 表示項目

### メディアグリッド

| 項目名 | 表示形式 | 備考 |
|--------|---------|------|
| メディアサムネイル | 画像 | アスペクト比1:1で表示 |
| サムネイルバッジ | アイコン | サムネイル指定画像に表示 |
| 選択チェックボックス | チェックボックス | ホバー時または選択時に表示 |

### コマンドバー（選択時）

| 項目名 | 表示形式 | 備考 |
|--------|---------|------|
| 選択件数 | テキスト | 「X件選択中」形式 |
| サムネイル設定ボタン | ボタン | 1件選択時のみ表示 |
| 削除ボタン | ボタン | 常時表示 |

## イベント仕様

### 1-ファイルアップロード

**トリガー**：ファイル選択またはドラッグ&ドロップ

**処理フロー**：
1. 選択されたファイルをフォームのmedia配列に追加
2. プレビュー表示を更新
3. 保存時に`sdk.admin.upload.create`でファイルをアップロード

**関連コンポーネント**：`UploadMediaFormItem`

### 2-ドラッグ&ドロップによる並び替え

**トリガー**：メディアアイテムのドラッグ操作

**処理フロー**：
1. `DndContext`の`onDragStart`でドラッグ開始を検知
2. `onDragEnd`でドロップ位置を確定
3. `arrayMove`関数で配列の順序を変更
4. `form.setValue`でフォーム値を更新

**使用ライブラリ**：@dnd-kit/core, @dnd-kit/sortable

### 3-サムネイル設定

**トリガー**：コマンドバーの「Make Thumbnail」ボタン押下（ショートカット：T）

**処理フロー**：
1. 選択アイテムが1件のみであることを確認
2. 現在のサムネイル指定を解除
3. 選択アイテムにサムネイルフラグを設定
4. 選択状態をクリア

### 4-メディア削除

**トリガー**：コマンドバーの「Delete」ボタン押下（ショートカット：D）

**処理フロー**：
1. 選択されたアイテムのインデックスを取得
2. `remove`関数で配列から削除
3. 選択状態をクリア

### 5-保存

**トリガー**：「Save」ボタン押下

**処理フロー**：
1. 新規アップロードが必要なファイルを抽出
2. `sdk.admin.upload.create`でファイルをアップロード
3. アップロード結果のURLで配列を更新
4. `useUpdateProduct`ミューテーションで商品データを更新
5. 成功時にトースト表示、モーダルを閉じる

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

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

| 操作（イベント） | 対象テーブル | 操作種別 | 概要 |
|----------------|-------------|---------|------|
| 保存ボタン押下 | product | UPDATE | 商品のimages、thumbnailフィールドを更新 |
| ファイルアップロード | file | INSERT | 新規ファイルをストレージに保存 |

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

#### product

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| UPDATE | images | メディア配列（URL、ID含む） | JSON配列形式 |
| UPDATE | thumbnail | サムネイル指定画像のURL | null可 |

## メッセージ仕様

| 種別 | メッセージキー | 表示条件 |
|------|--------------|---------|
| 成功 | products.media.successToast | 保存成功時 |
| エラー | products.media.failedToUpload | アップロード失敗時 |
| エラー | (API error message) | API呼び出し失敗時 |

## 例外処理

| 例外条件 | 処理内容 |
|---------|---------|
| 商品が見つからない | エラーをスロー（親コンポーネントでハンドリング） |
| ファイルアップロード失敗 | フォームエラーを設定、アップロード中断 |
| API呼び出し失敗 | エラートースト表示 |

## 備考

- 本画面はギャラリービューとエディットビューの2つのモードを持つ
- ギャラリービューではメディアのプレビューのみが可能
- エディットビューでは並び替え、削除、サムネイル設定が可能
- @dnd-kit/coreを使用したドラッグ&ドロップ実装により、直感的なUI操作を実現

---

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

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

### 推奨読解順序

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

まず、メディア管理で使用されるデータ構造を把握する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | constants.ts | `packages/admin/dashboard/src/routes/products/product-create/constants.ts` | MediaSchema、EditProductMediaSchemaの定義を確認 |
| 1-2 | types.ts | `packages/admin/dashboard/src/routes/products/product-create/types.ts` | EditProductMediaSchemaTypeの型定義を確認 |

**読解のコツ**: ZodスキーマによりTypeScriptの型とバリデーションルールが同時に定義されている。`z.infer`でスキーマから型を導出している点に注目。

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

処理の起点となるメインコンポーネントを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | product-media.tsx | `packages/admin/dashboard/src/routes/products/product-media/product-media.tsx` | ルートコンポーネント、商品データのフェッチ |

**主要処理フロー**:
1. **7-14行目**: useProductフックで商品データを取得
2. **18-20行目**: エラー時のスロー処理
3. **22-32行目**: RouteFocusModal内でProductMediaViewをレンダリング

#### Step 3: ビュー切り替えロジックを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | product-media-view.tsx | `packages/admin/dashboard/src/routes/products/product-media/components/product-media-view/product-media-view.tsx` | GalleryとEditの切り替え制御 |

**主要処理フロー**:
- **16-23行目**: URLパラメータからビューモードを取得
- **47-54行目**: ビューに応じてGalleryまたはEditFormをレンダリング

#### Step 4: 編集フォームの実装を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | edit-product-media-form.tsx | `packages/admin/dashboard/src/routes/products/product-media/components/edit-product-media-form/edit-product-media-form.tsx` | メインのフォームロジック |

**主要処理フロー**:
- **52-68行目**: useFormフックによるフォーム初期化
- **72-77行目**: useSensorsによるドラッグセンサー設定
- **83-96行目**: ドラッグ終了時の配列並び替え処理
- **104-149行目**: handleSubmitによる保存処理
- **165-171行目**: メディア削除処理
- **173-197行目**: サムネイル昇格処理

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

```
ProductMedia (product-media.tsx)
    │
    ├─ useProduct (hooks/api/products.tsx)
    │      └─ sdk.admin.product.retrieve
    │
    └─ ProductMediaView (product-media-view.tsx)
           │
           ├─ ProductMediaGallery (ギャラリービュー)
           │
           └─ EditProductMediaForm (エディットビュー)
                  │
                  ├─ useForm (react-hook-form)
                  ├─ useFieldArray
                  ├─ useSensors (@dnd-kit)
                  ├─ useUpdateProduct
                  │      └─ sdk.admin.product.update
                  └─ sdk.admin.upload.create
```

### データフロー図

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

ファイル選択 ────────▶ UploadMediaFormItem ────────▶ フォーム状態更新
                              │
ドラッグ操作 ────────▶ DndContext ────────────────▶ 配列順序変更
                              │
チェックボックス ────▶ handleCheckedChange ────────▶ 選択状態更新
                              │
サムネイル設定 ──────▶ handlePromoteToThumbnail ──▶ isThumbnailフラグ更新
                              │
削除操作 ────────────▶ handleDelete ────────────────▶ 配列から削除
                              │
保存ボタン ──────────▶ handleSubmit ────────────────▶ API更新
                              │                              │
                              ├─ sdk.admin.upload.create ───▶ ファイルアップロード
                              │
                              └─ useUpdateProduct ──────────▶ 商品データ更新
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| product-media.tsx | `packages/admin/dashboard/src/routes/products/product-media/product-media.tsx` | ソース | ルートコンポーネント |
| product-media-view.tsx | `packages/admin/dashboard/src/routes/products/product-media/components/product-media-view/product-media-view.tsx` | ソース | ビュー切り替え制御 |
| edit-product-media-form.tsx | `packages/admin/dashboard/src/routes/products/product-media/components/edit-product-media-form/edit-product-media-form.tsx` | ソース | 編集フォーム本体 |
| product-media-gallery.tsx | `packages/admin/dashboard/src/routes/products/product-media/components/product-media-gallery/product-media-gallery.tsx` | ソース | ギャラリービュー |
| products.tsx | `packages/admin/dashboard/src/hooks/api/products.tsx` | ソース | 商品関連APIフック |
| constants.ts | `packages/admin/dashboard/src/routes/products/product-create/constants.ts` | ソース | スキーマ定義 |
| index.ts | `packages/admin/dashboard/src/routes/products/product-media/index.ts` | ソース | エクスポート定義 |
