# 画面設計書 32-カテゴリ整理画面

## 概要

本ドキュメントは、Medusa Admin Dashboard におけるカテゴリ整理画面の設計を記述したものです。

### 本画面の処理概要

商品カテゴリの階層構造と並び順をドラッグ&ドロップで整理するための画面です。カテゴリのツリー構造を視覚的に操作し、親子関係や表示順序を変更できます。

**業務上の目的・背景**：ECサイトにおけるカテゴリ階層は、顧客の商品検索体験とサイトのナビゲーション構造に直接影響します。カテゴリの親子関係を適切に設定し、表示順序を最適化することで、顧客が目的の商品に素早くたどり着けるようになります。季節やキャンペーンに応じてカテゴリの表示順を変更することで、売上向上にも貢献できます。

**画面へのアクセス方法**：カテゴリ一覧画面の「整理」ボタン押下、またはカテゴリ詳細画面のアクションメニューから「整理」を選択することで、フルスクリーンモーダル形式で表示されます。

**主要な操作・処理内容**：
1. カテゴリツリーの表示 - 全カテゴリを階層構造で表示
2. ドラッグ&ドロップでの並び替え - カテゴリの順序変更
3. ドラッグ&ドロップでの階層変更 - カテゴリの親子関係変更
4. 折りたたみ/展開 - 子カテゴリの表示/非表示切り替え
5. リアルタイム保存 - 操作ごとに即座にAPIで保存

**画面遷移**：カテゴリ一覧画面またはカテゴリ詳細画面から遷移してきます。操作完了後、元の画面に戻ります。

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

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 4 | 商品カテゴリ管理 | 主機能 | カテゴリの並び順・階層変更 |

## 画面種別

整理（フルスクリーンモーダル形式）

## URL/ルーティング

`/categories/organize`

## 入出力項目

| 項目名 | 項目ID | 型 | 必須 | 最大長 | 入力/出力 | 説明 |
|--------|--------|-----|------|--------|-----------|------|
| カテゴリツリー | product_categories | array | - | - | 出力 | 階層構造を持つカテゴリ配列 |
| カテゴリID | id | string | - | - | 入出力 | 移動対象のカテゴリID |
| 親カテゴリID | parent_category_id | string/null | - | - | 入出力 | 移動先の親カテゴリID |
| 順序 | rank | number | - | - | 入出力 | 兄弟カテゴリ内での順序（0始まり） |

## 表示項目

| 項目名 | 表示形式 | 説明 |
|--------|----------|------|
| カテゴリ名 | テキスト | 各カテゴリノードに表示される名前 |
| ローディングスピナー | アイコン | 更新処理中に表示 |
| 階層インデント | 視覚的インデント | 階層の深さを表現 |
| 展開/折りたたみアイコン | アイコン | 子カテゴリの表示/非表示切り替え |

## イベント仕様

### 1-カテゴリのドラッグ&ドロップ

カテゴリをドラッグして別の位置に移動すると、親カテゴリIDと順序（rank）が更新されます。

**処理フロー**:
1. ドラッグ開始時に対象カテゴリを視覚的にハイライト
2. ドロップ位置に応じて新しいparent_category_idとindexを算出
3. handleRankChange関数で新しい位置情報をAPIに送信
4. 楽観的更新（Optimistic Update）でUIを即座に更新
5. API成功後にクエリキャッシュを無効化
6. API失敗時は元の状態にロールバック

**API呼び出し**:
- エンドポイント: `POST /admin/product-categories/:id`
- リクエストボディ:
  ```json
  {
    "rank": 0,
    "parent_category_id": "cat_xxx" | null
  }
  ```

### 2-階層の展開/折りたたみ

カテゴリの展開アイコンをクリックすると、子カテゴリの表示/非表示が切り替わります。この操作はローカル状態のみで、APIへの保存は行われません。

### 3-モーダルクローズ

ヘッダーの閉じるボタンまたはESCキー押下でモーダルを閉じ、元の画面に戻ります。

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

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

| 操作（イベント） | 対象テーブル | 操作種別 | 概要 |
|----------------|-------------|---------|------|
| ドラッグ&ドロップ | product_category | UPDATE | カテゴリの親IDと順序を更新 |

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

#### product_category

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| UPDATE | parent_category_id | ドロップ先の親カテゴリID（ルートの場合はnull） | 親子関係の変更 |
| UPDATE | rank | ドロップ位置のindex（0始まり） | 兄弟内での順序 |
| UPDATE | updated_at | 現在日時 | 自動更新 |

## メッセージ仕様

| メッセージID | 種別 | メッセージ内容 | 表示タイミング |
|------------|------|--------------|--------------|
| - | エラー | {error.message} | API呼び出し失敗時（トースト表示） |

## 例外処理

| 例外状況 | 処理内容 |
|---------|---------|
| データ取得エラー | エラーをスローし、エラーバウンダリで処理 |
| API通信エラー | 楽観的更新をロールバックし、トーストでエラー表示 |
| 認証エラー | ログイン画面にリダイレクト |

## 備考

- フルスクリーンモーダル形式（RouteFocusModal）で表示
- SortableTreeコンポーネントを使用したドラッグ&ドロップ実装
- @dnd-kit/coreライブラリを使用
- 楽観的更新（Optimistic Update）パターンを採用し、UXを向上
- カテゴリ取得時はinclude_descendants_tree: trueで階層構造を取得
- 最大9999件のカテゴリを一度に取得（limit: 9999）

---

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

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

### 推奨読解順序

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

カテゴリツリーのデータ型を理解することが重要です。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | types.ts | `packages/admin/dashboard/src/routes/categories/common/types.ts` | CategoryTreeItem型の定義（id, name, parent_category_id, category_children, rank） |

**読解のコツ**: category_childrenが再帰的な構造であることを理解し、親子関係がparent_category_idで表現されていることを把握します。

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

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | category-organize.tsx | `packages/admin/dashboard/src/routes/categories/category-organize/category-organize.tsx` | RouteFocusModalでフルスクリーン表示、OrganizeCategoryFormを子コンポーネントとして配置 |

**主要処理フロー**:
1. **6-7行目**: RouteFocusModalでフルスクリーンモーダルを表示
2. **7行目**: OrganizeCategoryFormコンポーネントを配置

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

カテゴリ整理の主要ロジックを把握します。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | organize-category-form.tsx | `packages/admin/dashboard/src/routes/categories/category-organize/components/organize-category-form/organize-category-form.tsx` | useProductCategoriesでデータ取得、useMutationで更新、handleRankChangeで位置変更処理 |

**主要処理フロー**:
- **19-24行目**: QUERYオブジェクトでカテゴリ取得パラメータ定義（include_descendants_tree: true）
- **27-33行目**: useProductCategoriesでカテゴリツリーを取得
- **36-89行目**: useMutationで更新処理を定義（楽観的更新パターン）
- **52-73行目**: onMutateで楽観的更新（クエリキャンセル→スナップショット→即時反映）
- **75-83行目**: onErrorでロールバック処理
- **91-107行目**: handleRankChangeで位置変更をmutateAsyncに委譲

#### Step 4: カテゴリツリーコンポーネントを理解する

ツリー表示とドラッグ&ドロップの実装を把握します。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | category-tree.tsx | `packages/admin/dashboard/src/routes/categories/common/components/category-tree/category-tree.tsx` | SortableTreeコンポーネントを使用、childrenProp: "category_children"で子要素を指定 |

**主要処理フロー**:
- **40-48行目**: SortableTreeに必要なpropsを渡す（items, childrenProp, collapsible, enableDrag, onChange, renderValue）

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

```
CategoryOrganize (category-organize.tsx)
    │
    └─ RouteFocusModal
           │
           └─ OrganizeCategoryForm (organize-category-form.tsx)
                  │
                  ├─ useProductCategories(QUERY) → カテゴリツリー取得
                  │      └─ sdk.admin.productCategory.list()
                  │
                  ├─ useMutation() → 更新ミューテーション（楽観的更新）
                  │      ├─ mutationFn → sdk.admin.productCategory.update()
                  │      ├─ onMutate → 楽観的更新
                  │      ├─ onError → ロールバック
                  │      └─ onSettled → クエリ無効化
                  │
                  └─ CategoryTree (category-tree.tsx)
                         └─ SortableTree
                                └─ onChange → handleRankChange
```

### データフロー図

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

APIリクエスト ─────────▶ useProductCategories ───▶ カテゴリツリー表示
                              │
                              ▼
ドラッグ&ドロップ ───────▶ handleRankChange
                              │
                              ▼
                        useMutation (楽観的更新)
                              │
                    ┌─────────┴─────────┐
                    ▼                   ▼
              UIを即座に更新      SDK API呼び出し
                    │                   │
                    │                   ▼
                    │            成功 → キャッシュ無効化
                    │            失敗 → ロールバック
                    ▼
              ツリー表示更新
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| category-organize.tsx | `packages/admin/dashboard/src/routes/categories/category-organize/category-organize.tsx` | ソース | ルートコンポーネント |
| organize-category-form.tsx | `packages/admin/dashboard/src/routes/categories/category-organize/components/organize-category-form/organize-category-form.tsx` | ソース | 整理フォームコンポーネント |
| index.ts | `packages/admin/dashboard/src/routes/categories/category-organize/index.ts` | ソース | エクスポート定義 |
| category-tree.tsx | `packages/admin/dashboard/src/routes/categories/common/components/category-tree/category-tree.tsx` | ソース | ツリー表示コンポーネント |
| types.ts | `packages/admin/dashboard/src/routes/categories/common/types.ts` | ソース | カテゴリツリーの型定義 |
| categories.tsx | `packages/admin/dashboard/src/hooks/api/categories.tsx` | ソース | カテゴリAPI フック |
| RouteFocusModal | `packages/admin/dashboard/src/components/modals/` | ソース | フルスクリーンモーダルコンポーネント |
| SortableTree | `packages/admin/dashboard/src/components/common/sortable-tree/` | ソース | ソート可能ツリーコンポーネント |
