# 機能設計書 98-ダッシュボード

## 概要

本ドキュメントは、Medusaコマースプラットフォームにおける管理ダッシュボード機能の設計仕様を記述する。ダッシュボード機能は、ストア運営者が注文、商品、顧客、在庫などのEコマース業務を管理するためのReactベースの管理画面を提供する。

### 本機能の処理概要

ダッシュボード機能は、プラグインベースのアーキテクチャを採用したSPA（Single Page Application）として実装されている。DashboardAppクラスがコアとなり、ウィジェット、メニュー、ルート、フォーム、ディスプレイ、国際化リソースの管理を行う。React Routerを使用したクライアントサイドルーティングにより、シームレスなユーザー体験を提供する。

**業務上の目的・背景**：ECプラットフォームの管理業務には、注文処理、在庫管理、顧客対応、プロモーション設定など多岐にわたる作業が含まれる。これらの業務を効率的に行うためには、直感的で拡張可能な管理インターフェースが必要である。プラグインアーキテクチャにより、カスタム機能の追加やサードパーティ統合が容易になる。

**機能の利用シーン**：
- 注文一覧の確認と処理（発送、返品、払い戻し）
- 商品の登録・編集・在庫管理
- 顧客情報の管理とセグメンテーション
- プロモーション・キャンペーンの作成と管理
- 配送・決済設定の構成
- ストア設定の管理

**主要な処理内容**：
1. プラグイン統合 - ウィジェット、メニュー、ルート、フォームの登録
2. ルーティング - React Routerを使用したページナビゲーション
3. 認証 - ProtectedRouteによるアクセス制御
4. 国際化 - 多言語サポート
5. カスタムフィールド - モデルごとのフォーム拡張

**関連システム・外部連携**：
- Medusa Backend API（REST API経由）
- 認証システム（ログイン、パスワードリセット、招待）

**権限による制御**：ProtectedRouteコンポーネントにより、認証済みユーザーのみがダッシュボードにアクセス可能。

## 関連画面

| 画面No | 画面名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 185 | ワークフロー実行一覧 | 表示 | ワークフロー実行履歴の表示 |
| 186 | ワークフロー実行詳細 | 表示 | ワークフロー実行詳細の表示 |
| - | 注文一覧/詳細 | CRUD | 注文管理 |
| - | 商品一覧/詳細 | CRUD | 商品管理 |
| - | 顧客一覧/詳細 | CRUD | 顧客管理 |
| - | 設定画面群 | 設定 | 各種ストア設定 |

## 機能種別

管理画面 / フロントエンド

## 入力仕様

### DashboardAppコンストラクタ入力

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| plugins | DashboardPlugin[] | Yes | ダッシュボードプラグイン配列 | - |

### DashboardPlugin構造

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| formModule | FormModule | Yes | カスタムフィールド定義 | - |
| displayModule | DisplayModule | Yes | 表示コンポーネント定義 | - |
| menuItemModule | MenuItemModule | Yes | メニュー項目定義 | - |
| widgetModule | WidgetModule | Yes | ウィジェット定義 | - |
| routeModule | RouteModule | Yes | ルート定義 | - |
| i18nModule | I18nModule | No | 国際化リソース | - |

### メニュー項目定義

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| label | string | Yes | メニュー表示ラベル | - |
| path | string | Yes | ルートパス | 動的パラメータを含む場合は警告 |
| icon | ComponentType | No | アイコンコンポーネント | - |
| nested | NestedRoutePosition | No | ネスト位置 | - |
| rank | number | No | 表示順序 | 昇順ソート |
| translationNs | string | No | 翻訳名前空間 | - |

### 入力データソース

- プラグインモジュール（virtual:medusa/* 経由で提供）
- ユーザー入力（フォーム、検索、フィルター）

## 出力仕様

### render()出力

| 項目名 | 型 | 説明 |
|--------|-----|------|
| JSX.Element | React.ReactNode | ルーターを含むReactコンポーネントツリー |

### API公開メソッド

| メソッド名 | 戻り値 | 説明 |
|-----------|--------|------|
| getMenu(path) | INavItem[] | 指定パスのメニュー項目を取得 |
| getWidgets(zone) | ComponentType[] | 指定ゾーンのウィジェットを取得 |
| getFormFields(model, zone, tab?) | FormField[] | カスタムフォームフィールドを取得 |
| getFormConfigs(model, zone) | ConfigField[] | フォーム設定を取得 |
| getDisplays(model, zone) | ComponentType[] | 表示コンポーネントを取得 |
| getI18nResources() | I18nExtension | 国際化リソースを取得 |

### 出力先

- ブラウザDOM

## 処理フロー

### 処理シーケンス（アプリケーション初期化）

```
1. Appコンポーネントマウント
   └─ DashboardAppインスタンス生成

2. プラグイン処理
   ├─ populateWidgets() - ウィジェット登録
   ├─ populateMenus() - メニュー登録
   ├─ populateRoutes() - ルート登録
   ├─ populateForm() - フォームフィールド・設定登録
   ├─ populateDisplays() - 表示コンポーネント登録
   └─ populateI18n() - 国際化リソース統合

3. ルーター生成
   └─ createBrowserRouter(routes)

4. レンダリング
   └─ <Providers api={this.api}><RouterProvider /></Providers>
```

### フローチャート

```mermaid
flowchart TD
    A[App マウント] --> B[DashboardApp 初期化]
    B --> C[プラグイン処理]
    C --> D[populateWidgets]
    C --> E[populateMenus]
    C --> F[populateRoutes]
    C --> G[populateForm]
    C --> H[populateDisplays]
    C --> I[populateI18n]
    D --> J[render 呼び出し]
    E --> J
    F --> J
    G --> J
    H --> J
    I --> J
    J --> K[createBrowserRouter]
    K --> L[RouterProvider レンダリング]
    L --> M{認証状態?}
    M -->|未認証| N[PublicLayout - ログイン画面]
    M -->|認証済| O[ProtectedRoute]
    O --> P[MainLayout / SettingsLayout]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-98-1 | 動的パラメータパス警告 | 必須パラメータを含むパスはメニューに追加不可 | メニュー登録時 |
| BR-98-2 | ネスト設定パス制限 | 設定ページは2階層以上のネストは不可 | 設定メニュー登録時 |
| BR-98-3 | メニューランクソート | ランク値で昇順ソート、未定義は最後 | メニュー表示時 |
| BR-98-4 | ホームリダイレクト | ホーム（/）へのアクセスは/ordersにリダイレクト | ホームページアクセス時 |
| BR-98-5 | プラグイン優先順位 | ローカルプラグインが最初に適用 | プラグイン初期化時 |

### 計算ロジック

メニュー項目ソート：
```typescript
// ランクによる昇順ソート（未定義は最後）
sortMenuItemsByRank(items: INavItem[])
```

プロバイダー登録識別子の生成：
```typescript
// オプショナルパスセグメントの除去
path.replace(/\/([^\/])+\?$/, "")
```

## データベース操作仕様

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

| 操作 | 対象テーブル | 操作種別 | 概要 |
|-----|-------------|---------|------|
| - | - | - | フロントエンド専用（直接のDB操作なし） |

※ダッシュボードからのAPI呼び出しにより、間接的に各種テーブルへのCRUD操作が発生する。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------
| - | 警告（console.warn） | 動的パラメータを含むパスのメニュー登録 | パスから動的部分を除去 |
| - | 警告（console.warn） | 設定ページの2階層以上ネスト | 1階層のみにする |
| - | 警告（console.warn） | ネストされたルート下へのメニュー追加 | 親ルートの設定を確認 |
| - | ErrorBoundary | ルートコンポーネントでの例外 | エラー画面表示 |

### リトライ仕様

- フロントエンドコンポーネントレベルでのリトライはなし
- API呼び出しのリトライはAPIクライアント層で実装

## トランザクション仕様

フロントエンド専用のため、トランザクション管理はバックエンドAPI側で行われる。

## パフォーマンス要件

- 遅延ローディング（lazy loading）によるルートコンポーネントの分割
- 仮想モジュール（virtual:medusa/*）によるビルド時最適化
- React Routerのプリロード機能活用

## セキュリティ考慮事項

- ProtectedRouteによる認証チェック
- XSS対策（React標準のエスケープ処理）
- CSRFトークン対応（API呼び出し時）

## 備考

- ベースパスは`__BASE__`変数で設定可能
- 開発モード時のみ警告メッセージを出力（NODE_ENV === "development"）
- i18nextを使用した国際化対応

---

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

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

### 推奨読解順序

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

アプリケーションの起点となるファイルを確認。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | app.tsx | `packages/admin/dashboard/src/app.tsx` | Appコンポーネントとプラグイン初期化 |

**読解のコツ**:
- 1-7行目：DashboardAppクラスとプラグイン型のインポート
- 4-9行目：virtual:medusa/* からの各種モジュールインポート
- 13-20行目：ローカルプラグインの構成
- 26-31行目：DashboardAppインスタンス化とレンダリング

#### Step 2: DashboardAppクラスを理解する

コアとなるDashboardAppクラスの実装を確認。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | dashboard-app.tsx | `packages/admin/dashboard/src/dashboard-app/dashboard-app.tsx` | DashboardAppクラスの全体構造 |

**主要処理フロー**:
- **53-76行目**: コンストラクタでの各種populate処理呼び出し
- **78-97行目**: populateRoutes()でコア/設定ルートを分離
- **99-119行目**: populateWidgets()でウィジェットをゾーン別に登録
- **121-217行目**: populateMenus()でメニュー階層を構築
- **219-277行目**: populateForm()でカスタムフィールドを処理
- **403-413行目**: populateI18n()で国際化リソースをマージ
- **472-481行目**: APIオブジェクトでゲッターメソッドを公開
- **483-498行目**: render()でルーターを生成しProviderでラップ

#### Step 3: 型定義を理解する

プラグインシステムの型定義を確認。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | types.ts | `packages/admin/dashboard/src/dashboard-app/types.ts` | DashboardPlugin型と関連型 |

**主要な型**:
- **14-20行目**: RouteExtension - ルート拡張定義
- **22-29行目**: MenuItemExtension - メニュー項目定義
- **31-34行目**: WidgetExtension - ウィジェット定義
- **136-143行目**: DashboardPlugin - プラグイン全体構造

#### Step 4: ルートマップを理解する

ルーティング構造の全体像を確認。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | get-route.map.tsx | `packages/admin/dashboard/src/dashboard-app/routes/get-route.map.tsx` | ルート定義とレイアウト構造 |

**主要構造**:
- **19-917行目**: MainLayout配下のコアルート（商品、注文、顧客など）
- **918-1876行目**: SettingsLayout配下の設定ルート
- **1877-1903行目**: PublicLayout配下の公開ルート（ログイン、招待など）

#### Step 5: ホームページの動作を理解する

ホームページのリダイレクト動作を確認。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | home.tsx | `packages/admin/dashboard/src/routes/home/home.tsx` | ホームから注文へのリダイレクト |

**処理内容**:
- **7-8行目**: useEffect内でnavigate("/orders", { replace: true })を呼び出し

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

```
App (app.tsx)
    │
    ├─ new DashboardApp({ plugins })
    │      │
    │      ├─ populateWidgets(plugins)
    │      │      └─ Map<InjectionZone, ComponentType[]>
    │      │
    │      ├─ populateMenus(plugins)
    │      │      ├─ allMenuItems.sort() パス長でソート
    │      │      ├─ path検証（動的パラメータチェック）
    │      │      ├─ 設定パスネストチェック
    │      │      ├─ 親子関係構築
    │      │      └─ sortMenuItemsByRank() ランクソート
    │      │
    │      ├─ populateRoutes(plugins)
    │      │      ├─ getRouteExtensions(routeModule, "core")
    │      │      ├─ getRouteExtensions(routeModule, "settings")
    │      │      └─ createRouteMap(routes)
    │      │
    │      ├─ populateForm(plugins)
    │      │      ├─ processFields(forms)
    │      │      │      └─ processFieldDefinition()
    │      │      │             ├─ getOrCreateZoneStructure()
    │      │      │             ├─ createFormField()
    │      │      │             └─ addFormFieldToZoneStructure()
    │      │      │
    │      │      └─ processConfigs(configs)
    │      │
    │      ├─ populateDisplays(plugins)
    │      │      └─ processDisplays()
    │      │
    │      └─ populateI18n(plugins)
    │             └─ deepMerge(coreTranslations, plugin.i18nModule)
    │
    └─ app.render()
           │
           ├─ getRouteMap({ settingsRoutes, coreRoutes })
           │      │
           │      ├─ ProtectedRoute + MainLayout
           │      │      └─ /products, /orders, /customers...
           │      │
           │      ├─ ProtectedRoute + SettingsLayout
           │      │      └─ /settings/*, settingsRoutes
           │      │
           │      └─ PublicLayout
           │             └─ /login, /reset-password, /invite
           │
           ├─ createBrowserRouter(routes, { basename: __BASE__ })
           │
           └─ <Providers api={this.api}>
                  <RouterProvider router={router} />
              </Providers>
```

### データフロー図

```
[プラグイン]                          [DashboardApp]                    [UI]

widgetModule.widgets ──────────────▶ populateWidgets() ──▶ WidgetMap
                                           │
menuItemModule.menuItems ──────────▶ populateMenus() ────▶ MenuMap ────▶ サイドバー
                                           │
routeModule.routes ────────────────▶ populateRoutes() ──▶ RouteObject[] ─▶ ルーティング
                                           │
formModule.customFields ───────────▶ populateForm() ────▶ FormFieldMap ──▶ フォーム拡張
                                           │
displayModule.displays ────────────▶ populateDisplays() ▶ DisplayMap ───▶ 詳細画面
                                           │
i18nModule.resources ──────────────▶ populateI18n() ────▶ I18nExtension ▶ 翻訳表示


[ユーザー操作]                        [React Router]                   [API]

URL変更 ───────────────────────────▶ RouterProvider
                                           │
                                    ProtectedRoute
                                           │
                                    認証チェック
                                           │
                              ┌────────────┴────────────┐
                              │                         │
                         未認証                       認証済
                              │                         │
                              ▼                         ▼
                    PublicLayout              MainLayout/SettingsLayout
                         │                              │
                    /login                    /orders, /products...
                                                        │
                                                   lazy import
                                                        │
                                                コンポーネント描画
                                                        │
                                                   API呼び出し ────▶ Medusa Backend
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| app.tsx | `packages/admin/dashboard/src/app.tsx` | ソース | アプリケーションエントリーポイント |
| dashboard-app.tsx | `packages/admin/dashboard/src/dashboard-app/dashboard-app.tsx` | ソース | ダッシュボードコアクラス |
| types.ts | `packages/admin/dashboard/src/dashboard-app/types.ts` | 型定義 | プラグインシステム型定義 |
| get-route.map.tsx | `packages/admin/dashboard/src/dashboard-app/routes/get-route.map.tsx` | ソース | ルート定義マップ |
| home.tsx | `packages/admin/dashboard/src/routes/home/home.tsx` | ソース | ホームページ（リダイレクト） |
| providers/index.ts | `packages/admin/dashboard/src/providers/index.ts` | ソース | プロバイダーエクスポート |
