# 画面設計書 40-コレクションメタデータ編集画面

## 概要

本ドキュメントは、Medusa Admin Dashboard におけるコレクションメタデータ編集画面の設計を記述したものです。

### 本画面の処理概要

商品コレクションのメタデータ（カスタム属性）を編集するための画面です。キーと値のペアを追加・編集・削除できます。

**業務上の目的・背景**：ECサイト運営において、コレクションに追加の属性情報を付与する必要がある場合があります。SEO用のキーワード、表示順序、外部システムとの連携用ID、カスタムフラグなど、標準フィールドでは対応できない情報をメタデータとして管理できます。この画面では、キー・バリュー形式で柔軟にメタデータを編集できます。

**画面へのアクセス方法**：コレクション詳細画面のメタデータセクションから「編集」リンク押下で、右側からドロワー形式で表示されます。

**主要な操作・処理内容**：
1. 既存メタデータの表示 - 現在のキー・バリューペアをテーブル形式で表示
2. メタデータの編集 - キーと値の変更
3. 新規行の追加 - 「上に行を追加」「下に行を追加」メニューから
4. 行の削除 - 各行のアクションメニューから削除
5. 保存ボタン押下で変更内容を確定

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

**権限による表示制御**：管理者権限を持つユーザーのみがメタデータを編集できます。認証されていないユーザーはログイン画面にリダイレクトされます。

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 5 | 商品コレクション管理 | 主機能 | コレクションメタデータの編集 |

## 画面種別

編集（ドロワー形式）

## URL/ルーティング

`/collections/:id/metadata`

## 入出力項目

| 項目名 | 項目ID | 型 | 必須 | 最大長 | 入力/出力 | 説明 |
|--------|--------|-----|------|--------|-----------|------|
| コレクションID | id | string | - | - | 入力(URL) | 編集対象のコレクションID |
| メタデータ配列 | metadata | array | - | - | 入出力 | キー・バリュー・disabledのオブジェクト配列 |
| キー | key | string | - | - | 入出力 | メタデータのキー |
| バリュー | value | any | - | - | 入出力 | メタデータの値（文字列、数値、真偽値、オブジェクト、配列） |
| 編集不可フラグ | disabled | boolean | - | - | 出力 | 複雑な型の場合true |

## 表示項目

| 項目名 | 表示形式 | 説明 |
|--------|----------|------|
| ヘッダータイトル | Heading | "Edit Metadata"（i18n: metadata.edit.header） |
| キーラベル | ラベル | "Key"（i18n: metadata.edit.labels.key） |
| バリューラベル | ラベル | "Value"（i18n: metadata.edit.labels.value） |
| メタデータグリッド | テーブル | キー・バリューの入力欄（行ごとのアクションメニュー付き） |
| 複雑型警告 | InlineTip | 編集不可の複雑な型がある場合の警告（i18n: metadata.edit.complexRow.label） |
| キャンセルボタン | Button | "Cancel"（i18n: actions.cancel） |
| 保存ボタン | Button | "Save"（i18n: actions.save） |

## イベント仕様

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

フォームの内容を解析し、APIを呼び出してメタデータを更新します。

**処理フロー**:
1. フォームデータをparseValues関数で変換
2. 削除されたキーを空文字で上書き
3. 値の型を自動変換（"true"/"false"→boolean、数値文字列→number）
4. `useUpdateCollection` フックを使用してAPI呼び出し
5. 成功時: トースト通知（metadata.edit.successToast）を表示し、ドロワーを閉じる
6. 失敗時: エラーメッセージをトーストで表示

**API呼び出し**:
- エンドポイント: `POST /admin/collections/:id`
- リクエストボディ:
  ```json
  {
    "metadata": {
      "key1": "value1",
      "key2": 123,
      "deleted_key": ""
    }
  }
  ```

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

フォームの変更を破棄し、ドロワーを閉じてコレクション詳細画面に戻ります。

### 3-行追加（上に追加）

選択した行の上に新しい空行を挿入します。

**処理フロー**:
1. DropdownMenuから「Insert Row Above」を選択
2. insert(index, { key: "", value: "", disabled: false })を実行

### 4-行追加（下に追加）

選択した行の下に新しい空行を挿入します。

**処理フロー**:
1. DropdownMenuから「Insert Row Below」を選択
2. insert(index + 1, { key: "", value: "", disabled: false })を実行

### 5-行削除

選択した行を削除します。最後の行を削除した場合は空行を自動追加します。

**処理フロー**:
1. DropdownMenuから「Delete Row」を選択
2. remove(index)を実行
3. 最後の1行だった場合、insert(0, { key: "", value: "", disabled: false })を実行

### 6-複雑な型の処理

オブジェクトや配列などの複雑な型は編集不可として表示されます。

**処理フロー**:
1. getDefaultValues関数でstring/number/boolean以外の型を検出
2. disabled: trueを設定
3. 値の表示を"{ ... }"や"[ ... ]"のプレースホルダーに置換
4. InlineTipで警告メッセージを表示

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

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

| 操作（イベント） | 対象テーブル | 操作種別 | 概要 |
|----------------|-------------|---------|------|
| 保存ボタン押下 | product_collection | UPDATE | コレクションのmetadataカラムを更新 |

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

#### product_collection

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| SELECT | * | id = URLパラメータ | コレクション詳細取得 |
| UPDATE | metadata | フォームから変換されたJSONオブジェクト | 削除キーは空文字で上書き |
| UPDATE | updated_at | 現在日時 | 自動更新 |

## メッセージ仕様

| メッセージID | 種別 | メッセージ内容 | 表示タイミング |
|------------|------|--------------|--------------|
| metadata.edit.successToast | 成功 | メタデータを更新しました | 更新成功時 |
| - | エラー | {error.message} | API呼び出し失敗時 |
| metadata.edit.complexRow.label | 警告 | 複雑なデータ型 | 編集不可行がある場合 |
| metadata.edit.complexRow.description | 情報 | 一部のメタデータは複雑な構造のため編集できません | 編集不可行がある場合 |
| metadata.edit.complexRow.tooltip | 情報 | このフィールドは編集できません | 編集不可行ホバー時 |

## 例外処理

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

## 備考

- ドロワー形式（RouteDrawer）で表示
- 共通コンポーネントMetadataFormを使用
- キーボードショートカット対応（KeyboundForm）
- フォームの状態管理にはreact-hook-formを使用
- useFieldArrayで動的な行の追加・削除を管理
- バリデーションにはZodスキーマを使用（MetadataSchema、MetadataFieldSchema）
- 編集可能な型: string, number, boolean
- 編集不可の型: object, array（disabledフラグで制御）
- 値の自動型変換: "true"/"false"→boolean、数値文字列→number
- 削除されたキーは空文字で上書きしてバックエンドで削除
- ローディング中はスケルトン表示（PlaceholderInner）
- 方向対応（RTL/LTR）: useDocumentDirectionで取得

---

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

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

### 推奨読解順序

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

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | metadata-form.tsx | `packages/admin/dashboard/src/components/forms/metadata-form/metadata-form.tsx` | MetadataFieldSchema（42-46行目）とMetadataSchema（48-50行目）でバリデーションルールを確認 |

**読解のコツ**: keyはstring、valueはany型、disabledはオプショナルbooleanです。

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

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | collection-metadata.tsx | `packages/admin/dashboard/src/routes/collections/collection-metadata/collection-metadata.tsx` | useParamsでID取得、useCollectionでデータフェッチ、MetadataFormを呼び出し |

**主要処理フロー**:
1. **6行目**: useParamsでURLからコレクションIDを取得
2. **8行目**: useCollectionでコレクションデータをフェッチ
3. **10-12行目**: useUpdateCollectionでミューテーションフック取得
4. **14-16行目**: エラー時の例外スロー
5. **18-24行目**: MetadataFormを呼び出し、metadata、hook、isPending、isMutatingを渡す

#### Step 3: 共通メタデータフォームを理解する

メタデータ編集フォームの実装詳細を把握します。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | metadata-form.tsx | `packages/admin/dashboard/src/components/forms/metadata-form/metadata-form.tsx` | RouteDrawer、useFieldArray、行操作の実装を確認 |

**主要処理フロー**:
- **52-69行目**: MetadataFormでRouteDrawer表示、isPendingでスケルトン/InnerForm切り替え
- **84-89行目**: useFormでフォーム初期化、getDefaultValuesで既存データを変換
- **91-108行目**: handleSubmitでparseValuesを呼び出しAPI送信
- **110-113行目**: useFieldArrayでfields、insert、removeを取得
- **115-126行目**: deleteRow関数（最後の行削除時に空行追加）
- **128-134行目**: insertRow関数（上/下に行追加）
- **156-264行目**: fieldsをmapしてグリッド行を描画

#### Step 4: データ変換関数を理解する

メタデータの変換処理を把握します。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | metadata-form.tsx | `packages/admin/dashboard/src/components/forms/metadata-form/metadata-form.tsx` | getDefaultValues（333-367行目）とparseValues（369-428行目）の実装を確認 |

**主要処理フロー**:
- **333-344行目**: getDefaultValuesでmetadataが空の場合は空行1つを返す
- **346-366行目**: 編集不可の型はdisabled: trueを設定
- **369-381行目**: parseValuesで空のフォームはnullを返す
- **385-393行目**: 削除されたキーを空文字で上書き
- **413-424行目**: 値の型を自動変換（boolean、number）

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

```
CollectionMetadata (collection-metadata.tsx)
    │
    ├─ useParams() → コレクションID取得
    │
    ├─ useCollection(id) → コレクションデータ取得
    │      └─ sdk.admin.productCollection.retrieve()
    │
    ├─ useUpdateCollection(id) → 更新ミューテーション
    │      └─ sdk.admin.productCollection.update()
    │
    └─ MetadataForm (metadata-form.tsx)
           │
           ├─ RouteDrawer → ドロワー表示
           │
           ├─ isPending ? PlaceholderInner : InnerForm
           │
           └─ InnerForm
                  │
                  ├─ useForm() → フォーム状態管理
                  │      └─ getDefaultValues() → 既存データ変換
                  │
                  ├─ useRouteModal() → モーダル制御
                  │      └─ handleSuccess() → ドロワークローズ
                  │
                  ├─ useFieldArray() → 動的フィールド管理
                  │      ├─ fields → フィールド配列
                  │      ├─ insert() → 行追加
                  │      └─ remove() → 行削除
                  │
                  ├─ handleSubmit() → フォーム送信処理
                  │      └─ parseValues() → データ変換
                  │             └─ hook() → API呼び出し
                  │
                  └─ グリッド描画
                         ├─ GridInput (key) → キー入力
                         ├─ GridInput (value) → バリュー入力
                         └─ DropdownMenu → 行操作メニュー
```

### データフロー図

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

URLパラメータ(:id) ───▶ useCollection ───▶ コレクションデータ
                              │
                              ▼
                        collection.metadata
                              │
                              ▼
                        getDefaultValues() ───▶ フォーム初期値
                        (型変換、disabled判定)
                              │
                              ▼
グリッド編集 ──────────▶ useFieldArray
                        (fields, insert, remove)
                              │
                              ▼
保存ボタン押下 ─────────▶ handleSubmit
                              │
                              ▼
                        parseValues() ───▶ データ変換
                        (削除キー処理、型変換)
                              │
                              ▼
                        useUpdateCollection
                              │
                              ▼
                        SDK API呼び出し ───▶ metadata更新
                              │
                              ▼
                        toast.success() ───▶ 成功通知
                              │
                              ▼
                        handleSuccess() ───▶ ドロワークローズ
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| collection-metadata.tsx | `packages/admin/dashboard/src/routes/collections/collection-metadata/collection-metadata.tsx` | ソース | ルートコンポーネント |
| metadata-form.tsx | `packages/admin/dashboard/src/components/forms/metadata-form/metadata-form.tsx` | ソース | 共通メタデータフォーム |
| index.ts | `packages/admin/dashboard/src/routes/collections/collection-metadata/index.ts` | ソース | エクスポート定義 |
| collections.tsx | `packages/admin/dashboard/src/hooks/api/collections.tsx` | ソース | コレクションAPI フック |
| RouteDrawer | `packages/admin/dashboard/src/components/modals/` | ソース | ドロワーモーダル |
| Form | `packages/admin/dashboard/src/components/common/form/` | ソース | フォームコンポーネント |
| KeyboundForm | `packages/admin/dashboard/src/components/utilities/keybound-form/` | ソース | キーバインド対応フォーム |
| ConditionalTooltip | `packages/admin/dashboard/src/components/common/conditional-tooltip/` | ソース | 条件付きTooltip |
| InlineTip | `@medusajs/ui` | ライブラリ | インライン警告表示 |
