# 画面設計書 14-ワークスペースダッシュボード

## 概要

本ドキュメントは、OpenCodeコンソールのワークスペースダッシュボード画面の設計書である。Zen使用状況、モデル選択、プロバイダー設定、使用量グラフなどを表示するワークスペースのメイン画面である。

### 本画面の処理概要

ワークスペースダッシュボードは、Zenサービスの使用状況を一元的に確認し、各種設定を行うためのメイン画面である。残高表示、使用量グラフ、利用可能なモデルの確認、プロバイダー設定などの機能を提供し、管理者ユーザーには追加の管理機能（課金有効化、グラフ表示、プロバイダー設定）を提供する。

**業務上の目的・背景**：Zenは従量課金制のAIモデルアクセスサービスであり、ユーザーは残高をチャージしてモデルを利用する。本ダッシュボードは、現在の残高確認、使用状況の可視化、利用可能なモデルの確認を一箇所で行うことで、効率的なリソース管理を実現する。

**画面へのアクセス方法**：認証後、ワークスペースを選択して`/workspace/:id`にアクセスする。ワークスペース詳細レイアウトのナビゲーションから「Zen」を選択してもアクセス可能。

**主要な操作・処理内容**：
1. 残高の確認と課金有効化（管理者）
2. 使用量グラフの確認（管理者）
3. 利用可能なモデルの確認
4. プロバイダー設定の確認（管理者）
5. 使用量セクションの確認
6. 新規ユーザー向けガイダンスの表示

**画面遷移**：ヘッダーバナーからドキュメント（/docs/zen）へ遷移可能。課金有効化ボタンでStripe Checkoutへリダイレクト。

**権限による表示制御**：管理者（isAdmin=true）のみが、課金情報表示、グラフセクション、プロバイダーセクションを閲覧可能。

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 26 | 使用量追跡 | 主機能 | Zen使用状況のグラフ表示、使用量セクション |
| 29 | モデル管理 | 補助機能 | モデル選択セクションの表示 |
| 27 | マルチプロバイダーサポート | 補助機能 | プロバイダーセクションの表示 |
| 56 | 国際化 | 補助機能 | ダッシュボードテキストの多言語表示 |

## 画面種別

ダッシュボード / 詳細

## URL/ルーティング

| 環境 | URL |
|------|-----|
| 本番 | `https://opencode.ai/workspace/:id` |
| 開発 | `http://localhost:3000/workspace/:id` |

ルーティング定義: `packages/console/app/src/routes/workspace/[id]/index.tsx`

## 入出力項目

### 入力項目

| 項目名 | 項目ID | データ型 | 必須 | 取得方法 | 備考 |
|--------|--------|----------|------|----------|------|
| ワークスペースID | params.id | string | Yes | URLパラメータ | useParams()で取得 |

### 出力項目（サーバーサイドクエリ）

#### querySessionInfo
| 項目名 | データ型 | 備考 |
|--------|----------|------|
| isAdmin | boolean | 管理者権限フラグ |
| isBeta | boolean | ベータ機能アクセスフラグ |

#### queryBillingInfo
| 項目名 | データ型 | 備考 |
|--------|----------|------|
| customerID | string | Stripe顧客ID |
| balance | number | 現在の残高（マイクロ単位） |
| reloadAmount | number | リロード金額 |
| paymentMethodID | string | 支払い方法ID |
| その他 | - | 請求関連情報 |

## 表示項目

### ヘッダーセクション

| 要素 | コンポーネント | 表示条件 | 説明 |
|------|---------------|----------|------|
| ロゴ | IconLogo | 常時 | Zenサービスロゴ |
| バナーテキスト | - | 常時 | Zenサービス説明、ドキュメントリンク |
| 残高表示 | - | isAdmin=true かつ customerID存在 | 現在の残高（$X.XX形式） |
| 課金有効化ボタン | button | isAdmin=true かつ customerID未設定 | Stripe Checkoutへ遷移 |

### セクション一覧

| セクション | コンポーネント | 表示条件 | 説明 |
|-----------|---------------|----------|------|
| NewUserSection | NewUserSection | 常時 | 新規ユーザー向けガイダンス |
| GraphSection | GraphSection | isAdmin=true | 使用量グラフ |
| ModelSection | ModelSection | 常時 | 利用可能なモデル一覧 |
| ProviderSection | ProviderSection | isAdmin=true | プロバイダー設定 |
| UsageSection | UsageSection | 常時 | 使用量詳細 |

## イベント仕様

### 1-課金有効化ボタンクリック

| 項目 | 内容 |
|------|------|
| トリガー | 「Enable Billing」ボタンクリック |
| 処理 | onClickCheckout関数が実行される |
| 詳細フロー | 1. createCheckoutUrl actionを実行<br>2. 成功時: checkoutRedirectingをtrueに設定<br>3. Stripe CheckoutページへリダイレクトURL取得<br>4. window.location.hrefでリダイレクト |

### 2-ドキュメントリンククリック

| 項目 | 内容 |
|------|------|
| トリガー | 「Learn more」リンククリック |
| 処理 | language.route("/docs/zen")で言語対応URLを生成 |
| 結果 | 新しいタブでドキュメントページを開く |

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

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

| 操作（イベント） | 対象テーブル | 操作種別 | 概要 |
|----------------|-------------|---------|------|
| ページロード | UserTable, BillingTable | SELECT | セッション情報、請求情報の取得 |
| 課金有効化 | BillingTable | INSERT/UPDATE | Stripe Checkout後にcustomerID設定 |

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

#### queryBillingInfo（common.tsxで定義）

```typescript
export const queryBillingInfo = query(async (workspaceID: string) => {
  return withActor(async () => {
    const billing = await Billing.get()
    return {
      customerID: billing.customerID,
      balance: billing.balance,
      // ... その他の請求情報
    }
  }, workspaceID)
}, "billing.get")
```

#### createCheckoutUrl（common.tsxで定義）

```typescript
export const createCheckoutUrl = action(
  async (workspaceID: string, amount: number, successUrl: string, cancelUrl: string) => {
    return withActor(
      () => Billing.generateCheckoutUrl({ amount, successUrl, cancelUrl }),
      workspaceID,
    )
  },
  "checkoutUrl",
)
```

## メッセージ仕様

| メッセージID | メッセージ種別 | 表示条件 | メッセージ内容（キー） |
|-------------|---------------|----------|----------------------|
| banner.beforeLink | テキスト | 常時 | workspace.home.banner.beforeLink |
| learnMore | リンク | 常時 | common.learnMore |
| billing.loading | ボタン | 処理中 | workspace.home.billing.loading |
| billing.enable | ボタン | customerID未設定 | workspace.home.billing.enable |
| billing.currentBalance | ラベル | customerID存在 | workspace.home.billing.currentBalance |

## 例外処理

| 例外パターン | 検出方法 | 対応処理 | ユーザーへの通知 |
|------------|----------|---------|----------------|
| Checkout URL取得失敗 | actionのエラーレスポンス | エラー内容を返却 | エラーメッセージ表示（子コンポーネント） |
| 請求情報取得失敗 | createAsyncエラー | billingInfo()がundefined | 課金関連UI非表示 |

## 備考

- 残高表示はformatBalance関数で100,000,000分の1に変換（マイクロ通貨単位）
- checkoutRedirectingフラグでリダイレクト中の二重クリック防止
- createStore/setStoreでローカル状態を管理
- 各セクションは独立したコンポーネントとして実装

---

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

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

### 推奨読解順序

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

本コンポーネントでは2つのサーバーサイドクエリでデータを取得する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | common.tsx | `packages/console/app/src/routes/workspace/common.tsx` | querySessionInfo（64-72行目）、queryBillingInfo（93-120行目） |
| 1-2 | common.tsx | `packages/console/app/src/routes/workspace/common.tsx` | formatBalance関数（36-39行目） |
| 1-3 | common.tsx | `packages/console/app/src/routes/workspace/common.tsx` | createCheckoutUrl action（74-91行目） |

**読解のコツ**: balance値は100,000,000を基準としたマイクロ通貨単位。formatBalanceで$X.XX形式に変換。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | index.tsx | `packages/console/app/src/routes/workspace/[id]/index.tsx` | デフォルトエクスポート関数（14-87行目） |

**主要処理フロー**:
1. **15行目**: useParams()でワークスペースID取得
2. **16-17行目**: useI18n()、useLanguage()でコンテキスト取得
3. **18-19行目**: createAsync()でセッション情報、請求情報取得
4. **20-21行目**: useAction()、useSubmission()でCheckoutアクション設定
5. **22-24行目**: createStoreでローカル状態管理
6. **25行目**: createMemo()でbalanceをリアクティブに計算
7. **27-34行目**: onClickCheckout関数 - Stripe Checkoutへのリダイレクト処理
8. **36-86行目**: JSXによるUI描画

#### Step 3: 子コンポーネントを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | new-user-section.tsx | `packages/console/app/src/routes/workspace/[id]/new-user-section.tsx` | 新規ユーザー向けガイダンス |
| 3-2 | graph-section.tsx | `packages/console/app/src/routes/workspace/[id]/graph-section.tsx` | 使用量グラフ |
| 3-3 | model-section.tsx | `packages/console/app/src/routes/workspace/[id]/model-section.tsx` | モデル一覧 |
| 3-4 | provider-section.tsx | `packages/console/app/src/routes/workspace/[id]/provider-section.tsx` | プロバイダー設定 |
| 3-5 | usage-section.tsx | `packages/console/app/src/routes/workspace/[id]/usage-section.tsx` | 使用量詳細 |

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

```
WorkspaceDashboard (index.tsx)
    |
    +-- useParams() -> params.id取得
    |
    +-- useI18n(), useLanguage() -> コンテキスト
    |
    +-- createAsync(querySessionInfo)
    |       +-- [SERVER] withActor() -> isAdmin判定
    |
    +-- createAsync(queryBillingInfo)
    |       +-- [SERVER] withActor()
    |               +-- Billing.get() -> 請求情報取得
    |
    +-- useAction(createCheckoutUrl)
    |       +-- [SERVER] Billing.generateCheckoutUrl()
    |
    +-- createMemo(formatBalance)
    |
    +-- onClickCheckout()
    |       +-- checkoutAction(params.id, amount, urls)
    |       +-- window.location.href = checkout.data
    |
    +-- セクション描画
            +-- <NewUserSection />
            +-- <GraphSection /> (isAdmin)
            +-- <ModelSection />
            +-- <ProviderSection /> (isAdmin)
            +-- <UsageSection />
```

### データフロー図

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

URLパラメータ ────────> useParams()
(params.id)                 |
                            v
            +---------------+---------------+
            |                               |
            v                               v
    querySessionInfo()            queryBillingInfo()
            |                               |
            v                               v
    isAdmin判定                      balance取得
            |                               |
            v                               v
    セクション表示制御         formatBalance() -> $X.XX表示
    (Graph, Provider)                       |
                                            v
                               +------------+------------+
                               |                         |
                               v                         v
                        customerID存在           customerID未設定
                               |                         |
                               v                         v
                        残高表示              課金有効化ボタン
                                                        |
                                                        v
                                            onClickCheckout()
                                                        |
                                                        v
                                            Stripe Checkoutへリダイレクト
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| index.tsx | `packages/console/app/src/routes/workspace/[id]/index.tsx` | ソース | メイン画面コンポーネント |
| common.tsx | `packages/console/app/src/routes/workspace/common.tsx` | ソース | 共通クエリ・アクション・ユーティリティ |
| new-user-section.tsx | `packages/console/app/src/routes/workspace/[id]/new-user-section.tsx` | ソース | 新規ユーザーセクション |
| graph-section.tsx | `packages/console/app/src/routes/workspace/[id]/graph-section.tsx` | ソース | グラフセクション |
| model-section.tsx | `packages/console/app/src/routes/workspace/[id]/model-section.tsx` | ソース | モデルセクション |
| provider-section.tsx | `packages/console/app/src/routes/workspace/[id]/provider-section.tsx` | ソース | プロバイダーセクション |
| usage-section.tsx | `packages/console/app/src/routes/workspace/[id]/usage-section.tsx` | ソース | 使用量セクション |
| icon.tsx | `packages/console/app/src/component/icon.tsx` | ソース | アイコンコンポーネント |
| billing.js | `packages/console-core/src/billing.js` | ソース | 請求ドメインロジック |
