# 画面設計書 29-カテゴリ作成画面

## 概要

本ドキュメントは、Medusa Admin Dashboard における「カテゴリ作成画面」の設計仕様を定義するものである。

### 本画面の処理概要

カテゴリ作成画面は、新しい商品カテゴリを作成するためのフルスクリーンモーダル形式の画面である。2つのステップ（詳細入力・階層設定）で構成されるウィザード形式で、カテゴリの基本情報と親カテゴリ・表示順序を設定できる。

**業務上の目的・背景**：ECサイトの商品整理において、新しいカテゴリを追加する必要がある。この画面により、カテゴリの名称・説明・ハンドル（URLスラッグ）・ステータス・可視性を設定し、既存のカテゴリ階層構造内での配置を視覚的に決定できる。親カテゴリを指定することで階層構造を構築し、ドラッグ&ドロップで表示順序を調整できる。

**画面へのアクセス方法**：カテゴリ一覧画面の「作成」ボタン、またはカテゴリ詳細画面のアクションメニューから子カテゴリ作成としてアクセス可能。URLは `/categories/create` の形式となる。親カテゴリIDはクエリパラメータで渡される（例：`/categories/create?parent_category_id=xxx`）。

**主要な操作・処理内容**：
1. Step 1（詳細）：カテゴリ名、ハンドル、説明、ステータス、可視性の入力
2. Step 2（整理）：カテゴリツリー内での配置をドラッグ&ドロップで設定
3. 保存によるカテゴリの新規作成

**画面遷移**：カテゴリ一覧画面またはカテゴリ詳細画面からモーダルとして開き、保存後は作成されたカテゴリの詳細画面に遷移する。キャンセル時は元の画面に戻る。

**権限による表示制御**：カテゴリ管理権限を持つユーザーのみがアクセス可能。

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 4 | 商品カテゴリ管理 | 主機能 | カテゴリの新規作成 |

## 画面種別

新規作成（フルスクリーンモーダル/ウィザード形式）

## URL/ルーティング

`/categories/create`

## 入出力項目

| 項目名 | 項目ID | データ型 | 必須 | 入力/出力 | 説明 |
|--------|--------|----------|------|-----------|------|
| 親カテゴリID | parent_category_id | string | - | 入力（URLクエリパラメータ） | 子カテゴリとして作成する場合の親ID |
| カテゴリ名 | name | string | 必須 | 入力 | カテゴリの名称（最低1文字） |
| ハンドル | handle | string | - | 入力 | URLスラッグ（省略時は自動生成） |
| 説明 | description | string | - | 入力 | カテゴリの説明文 |
| ステータス | status | "active" \| "inactive" | 必須 | 入力 | カテゴリのアクティブ状態 |
| 可視性 | visibility | "public" \| "internal" | 必須 | 入力 | 公開/内部の設定 |
| 表示順序 | rank | number | - | 入力 | 同階層内での表示順序 |

## 表示項目

| 項目名 | データ型 | 説明 |
|--------|----------|------|
| プログレスタブ | ProgressTabs | 詳細/整理の2ステップタブ |
| 詳細タブステータス | ProgressStatus | 入力完了で"completed"、進行中で"in-progress" |
| 整理タブステータス | ProgressStatus | 変更ありで"in-progress"、未開始で"not-started" |
| カテゴリ名入力 | Input | 名称入力フィールド |
| ハンドル入力 | HandleInput | スラッグ入力フィールド（先頭/付き） |
| 説明入力 | Textarea | 説明文入力エリア |
| ステータス選択 | Select | active/inactiveの選択 |
| 可視性選択 | Select | public/internalの選択 |
| カテゴリツリー | CategoryTree | 階層構造のドラッグ&ドロップ対応ツリー |
| 新規カテゴリバッジ | Badge | 新規作成中のカテゴリを示す青色バッジ |

## イベント仕様

### 1-詳細タブ入力

カテゴリ名、ハンドル、説明、ステータス、可視性を入力する。カテゴリ名は必須で1文字以上が必要。

### 2-続行ボタン押下（詳細タブ）

詳細タブの内容をCreateCategoryDetailsSchemaでバリデーションし、成功した場合は整理タブに遷移する。エラーがある場合はエラーメッセージを表示し、タブ遷移を阻止する。

### 3-整理タブでのドラッグ&ドロップ

カテゴリツリー内で新規カテゴリ（青色バッジ付き）をドラッグして配置を変更する。親カテゴリと表示順序（rank）がフォームに反映される。新規カテゴリのみドラッグ可能（既存カテゴリは固定）。

### 4-保存ボタン押下（整理タブ）

フォーム全体をバリデーションし、useCreateProductCategoryでAPIを呼び出してカテゴリを作成する。成功時はトースト通知を表示し、作成されたカテゴリの詳細画面に遷移する。

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

変更を破棄し、モーダルを閉じて元の画面に戻る。

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

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

| 操作（イベント） | 対象テーブル | 操作種別 | 概要 |
|----------------|-------------|---------|------|
| 保存ボタン押下 | product_category | INSERT | 新規カテゴリを作成 |

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

#### product_category

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | name | フォーム入力値 | 必須 |
| INSERT | handle | フォーム入力値またはnameから自動生成 | - |
| INSERT | description | フォーム入力値 | transformNullableFormDataで変換 |
| INSERT | is_active | status === "active" | - |
| INSERT | is_internal | visibility === "internal" | - |
| INSERT | parent_category_id | フォーム入力値（nullの場合はルート） | - |
| INSERT | rank | フォーム入力値 | 親がある場合の初期値は0 |

## メッセージ仕様

| メッセージID | 種別 | メッセージ内容 | 表示条件 |
|-------------|------|---------------|----------|
| categories.create.header | ヘッダー | カテゴリを作成 | 詳細タブヘッダー |
| categories.create.hint | 説明 | カテゴリの基本情報を入力してください | 詳細タブ説明 |
| categories.create.tabs.details | タブ | 詳細 | プログレスタブラベル |
| categories.create.tabs.organize | タブ | 整理 | プログレスタブラベル |
| categories.fields.new.label | バッジ | 新規 | ツリー内の新規カテゴリ表示 |
| categories.create.successToast | 成功 | {name}が作成されました | 作成成功時 |
| - | エラー | {エラーメッセージ} | 作成失敗時 |

## 例外処理

| 例外条件 | 処理内容 |
|----------|----------|
| カテゴリ一覧取得失敗 | エラーをthrowし、エラーバウンダリで処理 |
| バリデーションエラー | 各フィールドにエラーメッセージを表示 |
| API呼び出し失敗 | トースト通知でエラーメッセージを表示、shouldFreezeをfalseに戻す |

## 備考

- RouteFocusModalでフルスクリーンモーダル表示
- ProgressTabsで2ステップのウィザード形式
- zodでスキーマ定義（CreateCategoryDetailsSchema + rankとparent_category_id）
- タブ遷移時に詳細情報のバリデーションを実行
- shouldFreezeでAPI呼び出し中のツリー状態を固定
- 親カテゴリIDはURLクエリパラメータから取得可能
- transformNullableFormDataで空文字をnullに変換
- CategoryTreeコンポーネントでドラッグ&ドロップUI提供
- 新規カテゴリのみenableDragでドラッグ可能（既存は固定）
- insertCategoryTreeItemで新規カテゴリをツリーに挿入

---

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

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

### 推奨読解順序

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

フォームスキーマとバリデーションルールを理解する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | schema.ts | `packages/admin/dashboard/src/routes/categories/category-create/components/create-category-form/schema.ts` | CreateCategoryDetailsSchema（3-9行目）で詳細入力のバリデーション確認 |
| 1-2 | schema.ts | `packages/admin/dashboard/src/routes/categories/category-create/components/create-category-form/schema.ts` | CreateCategorySchema（12-17行目）でrankとparent_category_idの追加確認 |

**読解のコツ**: CreateCategoryDetailsSchemaは詳細タブのバリデーションに使用。CreateCategorySchemaはそれにrankとparent_category_idを追加。

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

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | category-create.tsx | `packages/admin/dashboard/src/routes/categories/category-create/category-create.tsx` | useSearchParamsでparent_category_idを取得（7-9行目） |
| 2-2 | category-create.tsx | `packages/admin/dashboard/src/routes/categories/category-create/category-create.tsx` | RouteFocusModalでモーダル表示（12-14行目） |

**主要処理フロー**:
1. **7行目**: useSearchParamsでURLクエリパラメータを取得
2. **9行目**: parent_category_idを抽出
3. **12-14行目**: RouteFocusModal内でCreateCategoryFormを描画

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

ウィザード形式のフォーム実装を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | create-category-form.tsx | `packages/admin/dashboard/src/routes/categories/category-create/components/create-category-form/create-category-form.tsx` | useFormでフォーム初期化（38-49行目） |
| 3-2 | create-category-form.tsx | `packages/admin/dashboard/src/routes/categories/category-create/components/create-category-form/create-category-form.tsx` | handleTabChangeでタブ遷移とバリデーション（51-79行目） |
| 3-3 | create-category-form.tsx | `packages/admin/dashboard/src/routes/categories/category-create/components/create-category-form/create-category-form.tsx` | handleSubmitでAPI呼び出し（83-114行目） |
| 3-4 | create-category-form.tsx | `packages/admin/dashboard/src/routes/categories/category-create/components/create-category-form/create-category-form.tsx` | nestingStatusとdetailsStatusの計算（116-125行目） |

**主要処理フロー**:
- **34行目**: activeTabでタブ状態管理
- **35行目**: validDetailsで詳細バリデーション完了フラグ
- **36行目**: shouldFreezeでAPI呼び出し中のツリー固定フラグ
- **51-79行目**: handleTabChangeで整理タブ遷移時に詳細バリデーション実行
- **83-114行目**: handleSubmitでデータ変換とAPI呼び出し
- **95-96行目**: is_activeとis_internalへの変換
- **106行目**: handleSuccessで詳細画面遷移

#### Step 4: 詳細タブを理解する

詳細入力フォームの実装を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | create-category-details.tsx | `packages/admin/dashboard/src/routes/categories/category-create/components/create-category-form/create-category-details.tsx` | name/handle入力（27-58行目） |
| 4-2 | create-category-details.tsx | `packages/admin/dashboard/src/routes/categories/category-create/components/create-category-form/create-category-details.tsx` | status/visibility選択（75-140行目） |

#### Step 5: 整理タブを理解する

階層設定のツリーUIを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | create-category-nesting.tsx | `packages/admin/dashboard/src/routes/categories/category-create/components/create-category-form/create-category-nesting.tsx` | useProductCategoriesで既存カテゴリ取得（27-33行目） |
| 5-2 | create-category-nesting.tsx | `packages/admin/dashboard/src/routes/categories/category-create/components/create-category-form/create-category-nesting.tsx` | useWatchでフォーム値を監視（35-48行目） |
| 5-3 | create-category-nesting.tsx | `packages/admin/dashboard/src/routes/categories/category-create/components/create-category-form/create-category-nesting.tsx` | insertCategoryTreeItemで新規カテゴリをツリーに挿入（50-60行目） |
| 5-4 | create-category-nesting.tsx | `packages/admin/dashboard/src/routes/categories/category-create/components/create-category-form/create-category-nesting.tsx` | handleChangeでドラッグ結果を反映（62-84行目） |

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

```
CategoryCreate (category-create.tsx:6-16)
    │
    ├─ useSearchParams() → parent_category_id取得
    │
    └─ CreateCategoryForm (create-category-form.tsx:28-208)
           │
           ├─ useForm(CreateCategorySchema) → フォーム状態管理
           │
           ├─ useCreateProductCategory() → カテゴリ作成
           │      └─ sdk.admin.productCategory.create()
           │
           ├─ handleTabChange() → タブ遷移＆バリデーション
           │      └─ CreateCategoryDetailsSchema.safeParse()
           │
           ├─ handleSubmit() → 保存処理
           │      └─ mutateAsync() → API呼び出し
           │             └─ handleSuccess() → 詳細画面遷移
           │
           ├─ ProgressTabs
           │      ├─ CreateCategoryDetails → 詳細入力フォーム
           │      │      ├─ Input(name)
           │      │      ├─ HandleInput(handle)
           │      │      ├─ Textarea(description)
           │      │      ├─ Select(status)
           │      │      └─ Select(visibility)
           │      │
           │      └─ CreateCategoryNesting → 階層設定
           │             │
           │             ├─ useProductCategories() → 既存カテゴリ取得
           │             │
           │             ├─ useWatch() × 3 → フォーム値監視
           │             │
           │             ├─ insertCategoryTreeItem() → 新規カテゴリ挿入
           │             │
           │             └─ CategoryTree → ドラッグ&ドロップUI
           │                    └─ onChange(handleChange) → 位置更新
           │
           └─ RouteFocusModal.Footer
                  ├─ Cancel → モーダルを閉じる
                  ├─ Continue(詳細) → 整理タブへ
                  └─ Save(整理) → 保存実行
```

### データフロー図

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

URLクエリ(parent_category_id) ───▶ useSearchParams ───▶ 初期値設定
                                          │
                                          ▼
                                  ┌─────────────────────┐
                                  │ CreateCategoryForm  │
                                  │  useForm(defaults)  │
                                  └─────────────────────┘
                                          │
                          ┌───────────────┴───────────────┐
                          ▼                               ▼
                    詳細タブ入力                     整理タブ
                (name, handle, etc.)              (CategoryTree)
                          │                               │
                          ▼                               │
                  続行ボタン押下                          │
                          │                               │
                          ▼                               │
              CreateCategoryDetailsSchema                 │
                   .safeParse()                          │
                          │                               │
                          ├──失敗──▶ エラー表示           │
                          │                               │
                          ├──成功──▶ 整理タブ遷移 ────────┘
                          │                               │
                                                          ▼
                                              useProductCategories
                                                          │
                                                          ▼
                                              insertCategoryTreeItem
                                                   (新規カテゴリ挿入)
                                                          │
ドラッグ&ドロップ ────────────────────────────────▶ handleChange
                                                          │
                                                          ▼
                                                form.setValue
                                              (parent_category_id, rank)
                                                          │
保存ボタン押下 ──────────────────────────────────▶ handleSubmit
                                                          │
                                                          ▼
                                              transformNullableFormData
                                                          │
                                                          ▼
                                                    mutateAsync ───▶ API作成
                                                          │
                                                          ▼
                                                  handleSuccess
                                                          │
                                                          ▼
                                              /categories/{id}に遷移
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| category-create.tsx | `packages/admin/dashboard/src/routes/categories/category-create/category-create.tsx` | ソース | 画面のエントリーポイント |
| create-category-form.tsx | `packages/admin/dashboard/src/routes/categories/category-create/components/create-category-form/create-category-form.tsx` | ソース | ウィザード形式のフォームコンポーネント |
| create-category-details.tsx | `packages/admin/dashboard/src/routes/categories/category-create/components/create-category-form/create-category-details.tsx` | ソース | 詳細タブの入力フォーム |
| create-category-nesting.tsx | `packages/admin/dashboard/src/routes/categories/category-create/components/create-category-form/create-category-nesting.tsx` | ソース | 整理タブのツリーUI |
| schema.ts | `packages/admin/dashboard/src/routes/categories/category-create/components/create-category-form/schema.ts` | ソース | zodスキーマ定義 |
| useCreateProductCategory | `packages/admin/dashboard/src/hooks/api/categories.ts` | ソース | カテゴリ作成フック |
| useProductCategories | `packages/admin/dashboard/src/hooks/api/categories.ts` | ソース | カテゴリ一覧取得フック |
| CategoryTree | `packages/admin/dashboard/src/routes/categories/common/components/category-tree/` | ソース | ドラッグ&ドロップ対応ツリーコンポーネント |
| RouteFocusModal | `packages/admin/dashboard/src/components/modals/` | ソース | フルスクリーンモーダルコンポーネント |
| ProgressTabs | `@medusajs/ui` | 外部 | ステップ表示付きタブコンポーネント |
