# 画面設計書 41-ユーザーディレクトリ画面

## 概要

本ドキュメントは、Actual Budgetアプリケーションの「ユーザーディレクトリ画面」の画面設計書です。マルチユーザー環境における管理者専用のユーザー管理機能を提供します。

### 本画面の処理概要

ユーザーディレクトリ画面は、マルチユーザーモードが有効な環境において、管理者がシステムに登録されたユーザーを一覧表示し、追加・編集・削除を行うための管理画面です。

**業務上の目的・背景**：マルチユーザー環境では、複数のユーザーが同一サーバー上で予算ファイルを作成・共有します。この画面は、管理者がユーザーアカウントの作成、権限（ロール）の割り当て、ユーザーの有効/無効化を行うために必要です。適切なユーザー管理により、セキュリティを確保しつつ、チームでの予算管理を実現します。OpenID認証との連携により、外部の認証プロバイダーで管理されているユーザー名をシステムに登録できます。

**画面へのアクセス方法**：サイドバーまたは設定画面から「User Directory」リンクをクリックしてアクセスします。URL直接アクセスは `/user-directory` です。この画面はマルチユーザーモードが有効かつ管理者権限（ADMINISTRATOR）を持つユーザーのみがアクセスできます。

**主要な操作・処理内容**：
1. ユーザー一覧の表示：登録済みユーザーを一覧形式で表示し、ユーザー名、表示名、ロール、有効/無効状態、サーバーオーナーフラグを確認できます
2. ユーザーの検索・フィルタリング：検索ボックスでユーザー名、表示名、ロールによるフィルタリングが可能です
3. ユーザーの追加：「Add new user」ボタンからユーザー編集モーダルを開き、新規ユーザーを登録できます
4. ユーザーの編集：各ユーザー行の「Edit」ボタンからユーザー情報を編集できます
5. ユーザーの一括削除：チェックボックスで選択したユーザーを一括削除できます（サーバーオーナーは削除不可）

**画面遷移**：
- 遷移元：設定画面、サイドバー、ログインユーザーメニュー
- 遷移先：ユーザー編集モーダル（edit-user）、予算ファイル選択画面（戻る操作時）

**権限による表示制御**：
- マルチユーザーモードが無効の場合、この画面自体が存在しません（ルートが登録されません）
- 管理者権限（ADMINISTRATOR）を持たないユーザーはアクセスが拒否されます
- サーバーオーナーは削除チェックボックスが表示されず、ロール変更も不可です

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 66 | ユーザー管理 | 主機能 | ユーザー一覧の表示・管理（管理者専用） |

## 画面種別

一覧 / 管理

## URL/ルーティング

- **パス**: `/user-directory`
- **ルーティング定義**: `packages/desktop-client/src/components/FinancesApp.tsx` 345-355行目
- **アクセス制御**: `ProtectedRoute` コンポーネントで `Permissions.ADMINISTRATOR` 権限チェック

## 入出力項目

### 入力項目

| 項目名 | 項目ID | 型 | 必須 | 説明 |
|--------|--------|-----|------|------|
| 検索フィルター | filter | string | - | ユーザー名、表示名、ロールでフィルタリング |
| ユーザー選択チェックボックス | selectedItems | Set<string> | - | 一括削除対象の選択 |

### 出力項目（ユーザー一覧表示）

| 項目名 | 型 | 説明 |
|--------|-----|------|
| ユーザーID | string | 内部ID（非表示） |
| ユーザー名 | string | OpenIDプロバイダーに登録されたユーザー名 |
| 表示名 | string | 画面表示用の名前（オプション） |
| ロール | string | Admin / Basic |
| 有効 | boolean | アカウントが有効かどうか |
| サーバーオーナー | boolean | サーバー管理者かどうか |

## 表示項目

### ヘッダー部

| 項目 | 幅 | 説明 |
|------|-----|------|
| 選択チェックボックス | 20px | 全選択/解除 |
| Username | flex | ユーザー名 |
| Display Name | 250px | 表示名 |
| Role | 100px | ロール |
| Enabled | 100px | 有効/無効 |
| Server Owner | 100px | オーナーフラグ |
| （編集ボタン用） | 80px | Editボタン |

### ユーザー行

| 項目 | 幅 | 説明 |
|------|-----|------|
| 選択チェックボックス | 20px | 個別選択（オーナーは非表示） |
| userName | flex | ユーザー名テキスト |
| displayName | 250px | 表示名テキスト |
| role | 100px | ロール表示（Admin/Basic） |
| enabled | 100px | チェックボックス（読み取り専用） |
| owner | 100px | チェックボックス（読み取り専用） |
| edit | 80px | Editボタン |

## イベント仕様

### 1-検索フィルター入力

ユーザーが検索ボックスに文字を入力すると、リアルタイムでユーザー一覧をフィルタリングします。

- **処理**: `onSearchChange` 関数（UserDirectory.tsx 109-115行目）
- **フィルタリング対象**: displayName、userName、role（大文字小文字を区別しない）
- **ページネーション**: フィルター変更時にページをリセット

### 2-ユーザー追加ボタン押下

「Add new user」ボタン押下時、ユーザー編集モーダルを表示します。

- **処理**: `onAddUser` 関数（UserDirectory.tsx 215-236行目）
- **遷移先モーダル**: `edit-user`
- **初期値**: 空のユーザー情報（userName: '', role: null, enabled: true, displayName: ''）

### 3-ユーザー編集ボタン押下

各ユーザー行の「Edit」ボタン押下時、該当ユーザーの編集モーダルを表示します。

- **処理**: `onEditUser` 関数（UserDirectory.tsx 196-213行目）
- **遷移先モーダル**: `edit-user`
- **パラメータ**: 選択したユーザーオブジェクト

### 4-ユーザー選択チェックボックス

チェックボックスでユーザーを選択/解除します。

- **処理**: `dispatchSelected` による状態更新（UserDirectoryRow.tsx 51-57行目）
- **Shift+クリック**: 範囲選択に対応
- **オーナーユーザー**: チェックボックスは表示されず選択不可

### 5-選択ユーザー削除ボタン押下

選択されたユーザーを一括削除します。

- **処理**: `onDeleteSelected` 関数（UserDirectory.tsx 154-194行目）
- **API呼び出し**: `user-delete-all`
- **削除後処理**: ユーザー一覧を再読み込み、選択状態をクリア

### 6-無限スクロール

一覧のスクロールにより追加データを読み込みます。

- **処理**: `loadMore` 関数（UserDirectory.tsx 150-152行目）
- **表示件数**: 初期100件、以降50件ずつ追加

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

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

| 操作（イベント） | 対象テーブル | 操作種別 | 概要 |
|----------------|-------------|---------|------|
| ユーザー追加 | users（サーバーDB） | INSERT | 新規ユーザーレコード作成 |
| ユーザー編集 | users（サーバーDB） | UPDATE | ユーザー情報の更新 |
| ユーザー削除 | users（サーバーDB） | DELETE | 選択ユーザーの削除 |
| ユーザー一覧取得 | users（サーバーDB） | SELECT | 全ユーザー情報の取得 |

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

#### users（サーバー側データベース）

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| INSERT | id | 自動生成UUID | - |
| INSERT/UPDATE | userName | 入力値 | OpenIDユーザー名 |
| INSERT/UPDATE | displayName | 入力値 | 表示名（オプション） |
| INSERT/UPDATE | role | ADMIN/BASIC | ユーザーロール |
| INSERT/UPDATE | enabled | true/false | 有効フラグ |
| SELECT | owner | - | サーバーオーナーフラグ（読み取り専用） |
| DELETE | - | WHERE id IN (...) | 選択されたIDで削除 |

## メッセージ仕様

| メッセージID | 種別 | タイトル | メッセージ内容 | 発生条件 |
|-------------|------|---------|---------------|---------|
| error | エラー | Error getting users | You are not logged in. | 未認証状態 |
| login-expired | エラー | Login expired | Login expired, please log in again. | トークン期限切れ |
| error | エラー | Something happened while deleting users | 各種エラーメッセージ | 削除処理失敗時 |
| - | 情報 | No users | No users | ユーザーが存在しない場合 |

## 例外処理

| 例外状況 | 対応処理 |
|---------|---------|
| 未認証（unauthorized） | エラー通知表示、ログイン画面への誘導 |
| トークン期限切れ（token-expired） | エラー通知表示、「Go to login」ボタンでサインアウト処理 |
| ユーザー削除失敗（not-all-deleted） | エラー通知表示（サーバーオーナー削除試行の可能性） |
| 権限不足 | ProtectedRouteにより画面アクセス自体が拒否される |
| APIエラー | エラー通知表示、画面は空のユーザー一覧を表示 |

## 備考

- この画面はマルチユーザーモード（`multiuserEnabled`）が有効な場合のみルーティングに登録されます
- サーバーオーナーは特別な扱いとなり、削除や無効化ができません
- ユーザー編集はモーダルダイアログ（EditUser）で行われます
- 表示名が未設定の場合、OpenIDプロバイダーからのログイン時に自動取得される場合があります
- 検索フィルターはクライアントサイドで処理されます（サーバーAPIへの追加リクエストなし）

---

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

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

### 推奨読解順序

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

まず、ユーザーエンティティの型定義を理解することが重要です。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | user.ts | `packages/loot-core/src/types/models/user.ts` | UserEntity、NewUserEntity型の定義。id、userName、displayName、role、enabled、ownerの各フィールドを確認 |
| 1-2 | user.ts | `packages/loot-core/src/shared/user.ts` | PossibleRoles定数の定義（ADMIN/BASIC） |

**読解のコツ**: TypeScriptの交差型（&）に注意。UserEntityはNewUserEntityを拡張してidとownerを追加しています。

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

処理の起点となるページコンポーネントを確認します。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | UserDirectoryPage.tsx | `packages/desktop-client/src/components/admin/UserDirectory/UserDirectoryPage.tsx` | ページ全体の構造、Pageコンポーネントでラップ |
| 2-2 | FinancesApp.tsx | `packages/desktop-client/src/components/FinancesApp.tsx` | ルーティング定義（345-355行目）、ProtectedRouteによる権限チェック |

**主要処理フロー**:
1. **12-40行目**: UserDirectoryPageコンポーネント定義
2. **19-20行目**: Pageコンポーネントでヘッダー「User Directory」を設定
3. **27行目**: UserDirectoryコンポーネントを呼び出し

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | UserDirectory.tsx | `packages/desktop-client/src/components/admin/UserDirectory/UserDirectory.tsx` | メインロジック、状態管理、APIコール |

**主要処理フロー**:
- **82-326行目**: UserDirectoryContentコンポーネント
- **85-88行目**: useState定義（allUsers、page、filter）
- **117-136行目**: loadUsers関数 - APIからユーザー一覧を取得
- **138-148行目**: useEffectでコンポーネントマウント時にユーザーをロード
- **154-194行目**: onDeleteSelected関数 - 選択ユーザーの一括削除
- **196-213行目**: onEditUser関数 - 編集モーダルを開く
- **215-236行目**: onAddUser関数 - 新規ユーザー追加モーダルを開く

#### Step 4: 表示コンポーネントを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | UserDirectoryHeader.tsx | `packages/desktop-client/src/components/admin/UserDirectory/UserDirectoryHeader.tsx` | テーブルヘッダーの定義 |
| 4-2 | UserDirectoryRow.tsx | `packages/desktop-client/src/components/admin/UserDirectory/UserDirectoryRow.tsx` | 各ユーザー行のレンダリング、選択・ホバー状態の管理 |

**主要処理フロー（UserDirectoryRow）**:
- **24-144行目**: memoizedコンポーネント定義
- **47-60行目**: オーナーでない場合のみSelectCellを表示
- **133-140行目**: Editボタンの定義

#### Step 5: API層を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 5-1 | app.ts | `packages/loot-core/src/server/admin/app.ts` | サーバーAPIハンドラーの定義 |

**主要処理フロー**:
- **37-58行目**: getUsers関数 - GET /admin/users/
- **60-87行目**: deleteAllUsers関数 - DELETE /admin/users
- **89-107行目**: addUser関数 - POST /admin/users/
- **109-127行目**: updateUser関数 - PATCH /admin/users/

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

```
FinancesApp（ルーティング）
    │
    ├─ ProtectedRoute（権限チェック）
    │      └─ UserDirectoryPage
    │             └─ UserDirectory
    │                    ├─ UserDirectoryHeader
    │                    ├─ UsersList
    │                    │      └─ UserDirectoryRow（複数）
    │                    └─ Buttons（Add/Delete）
    │
    └─ pushModal（edit-user）
           └─ EditUserFinanceApp（モーダル）
```

### データフロー図

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

ページ読み込み ───▶ loadUsers() ───▶ send('users-get') ───▶ allUsers状態更新 ───▶ 一覧表示
                          │
                          ▼
                    サーバーAPI
                   GET /admin/users/
                          │
                          ▼
                    ユーザー一覧JSON

検索入力 ───▶ onSearchChange() ───▶ filteredUsers計算 ───▶ 一覧フィルタリング

Add押下 ───▶ onAddUser() ───▶ pushModal('edit-user') ───▶ モーダル表示
                                      │
                                      ▼
                              EditUserFinanceApp
                                      │
                              保存時 send('user-add')
                                      │
                                      ▼
                              POST /admin/users/

Delete押下 ───▶ onDeleteSelected() ───▶ send('user-delete-all') ───▶ 一覧再読み込み
                                              │
                                              ▼
                                    DELETE /admin/users
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| UserDirectoryPage.tsx | `packages/desktop-client/src/components/admin/UserDirectory/UserDirectoryPage.tsx` | ソース | ページラッパー |
| UserDirectory.tsx | `packages/desktop-client/src/components/admin/UserDirectory/UserDirectory.tsx` | ソース | メインロジック |
| UserDirectoryHeader.tsx | `packages/desktop-client/src/components/admin/UserDirectory/UserDirectoryHeader.tsx` | ソース | テーブルヘッダー |
| UserDirectoryRow.tsx | `packages/desktop-client/src/components/admin/UserDirectory/UserDirectoryRow.tsx` | ソース | ユーザー行コンポーネント |
| EditUser.tsx | `packages/desktop-client/src/components/modals/EditUser.tsx` | ソース | ユーザー編集モーダル |
| app.ts | `packages/loot-core/src/server/admin/app.ts` | ソース | サーバーAPI定義 |
| user.ts | `packages/loot-core/src/types/models/user.ts` | 型定義 | ユーザーエンティティ型 |
| user.ts | `packages/loot-core/src/shared/user.ts` | ソース | ロール定数 |
| FinancesApp.tsx | `packages/desktop-client/src/components/FinancesApp.tsx` | ソース | ルーティング定義 |
| ProtectedRoute.tsx | `packages/desktop-client/src/auth/ProtectedRoute.tsx` | ソース | 権限チェックHOC |
