# 画面設計書 50-ニュースレター設定

## 概要

本ドキュメントは、Ghost管理画面の「ニュースレター設定」画面の設計仕様を定義する。この画面はニュースレターの一覧表示と管理機能を提供する。

### 本画面の処理概要

この画面では、サイトに設定されているニュースレターの一覧を表示し、新規追加・編集・並び替え・アーカイブ管理を行うことができる。Active/Archivedの2タブで管理する。

**業務上の目的・背景**：Ghostでは複数のニュースレターを作成し、異なるトピックや購読者層に対して配信できる。例えば、毎週の更新ニュースレターと月刊のプレミアムコンテンツニュースレターを別々に管理できる。

**画面へのアクセス方法**：管理画面のサイドバーから「Settings」→「Email」セクション→「Newsletters」カードにアクセスする。URLは`/ghost/#/settings`でnavid: `newsletters`となる。

**主要な操作・処理内容**：
1. ニュースレター一覧の表示（Active/Archivedタブ切り替え）
2. 新規ニュースレターの追加
3. ニュースレターの詳細編集（モーダル）
4. アクティブなニュースレターの並び替え（ドラッグ&ドロップ）
5. メールアドレス検証（verifyEmailToken）

**画面遷移**：設定画面のEmailセクションに直接表示される。ニュースレターをクリックまたは「Edit」ボタンで詳細編集モーダルが開く。「Add newsletter」で新規作成モーダルが開く。

**権限による表示制御**：Administrator以上のロールを持つユーザーのみがニュースレター設定にアクセスできる。

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 20 | ニュースレター管理 | 主機能 | ニュースレターの一覧・追加・編集・削除 |

## 画面種別

設定画面（一覧表示型）

## URL/ルーティング

- 画面URL: `/ghost/#/settings` (navid: `newsletters`)
- 編集モーダルURL: `/ghost/#/settings/newsletters/{id}`
- 新規作成モーダルURL: `/ghost/#/settings/newsletters/new`

## 入出力項目

| 項目名 | 種別 | データ型 | 必須 | 説明 |
|--------|------|---------|------|------|
| Tab selection | 入力 | string | 必須 | Active/Archivedの切り替え |

## 表示項目

### 一覧表示項目

| 項目名 | データ型 | 説明 |
|--------|---------|------|
| Newsletter name | string | ニュースレター名 |
| Newsletter description | string | ニュースレター説明（なければ'No description'） |
| Subscribers | number | アクティブな購読者数 |
| Delivered | number | 配信済み投稿数 |
| Drag indicator | Icon | 並び替え用ハンドル（Activeタブのみ） |
| Edit button | Button | 詳細編集へのリンク |

### タブ構成

| タブID | タブ名 | 表示内容 |
|--------|-------|---------|
| active-newsletters | Active | アクティブなニュースレター（sort_order順） |
| archived-newsletters | Archived | アーカイブ済みニュースレター |

## イベント仕様

### 1-タブ切り替え

タブをクリックすると：

1. `setSelectedTab`で選択タブを更新
2. 対応するニュースレター一覧を表示

### 2-ニュースレター追加

「Add newsletter」ボタンをクリックすると：

1. `openNewsletterModal()`関数が呼び出される
2. `updateRoute('newsletters/new')`で新規作成モーダルに遷移

### 3-ニュースレター編集

一覧のニュースレターをクリックまたは「Edit」ボタンをクリックすると：

1. `showDetails()`関数が呼び出される
2. `updateRoute('newsletters/{id}')`で詳細編集モーダルに遷移

### 4-並び替え（ドラッグ&ドロップ）

アクティブなニュースレターをドラッグ&ドロップすると：

1. `onSort`関数が呼び出される
2. `arrayMove`で新しい順序を計算
3. 各ニュースレターの`sort_order`を更新
4. ローカルステートとキャッシュを即時更新（楽観的UI）
5. APIに順番通りに`editNewsletter`を実行

### 5-もっと読み込む

ニュースレターが多い場合、「Load more」ボタンが表示される：

1. `fetchNextPage()`で次のページを取得
2. 無限スクロール対応

### 6-メール検証

URLにverifyEmailTokenがある場合：

1. `verifyEmail({token})`でトークンを検証
2. 成功時は確認モーダルを表示
   - sender_email検証時：「Newsletter email verified」
   - sender_reply_to検証時：「Reply-to address verified」
   - その他：「Email address verified」
3. 失敗時はエラーモーダルを表示
   - Token expired時：「Verification link has expired.」
   - その他：「There was an error verifying your email address. Try again later.」

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

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

| 操作（イベント） | 対象テーブル | 操作種別 | 概要 |
|----------------|-------------|---------|------|
| 並び替え | newsletters | UPDATE | sort_order更新 |
| メール検証 | newsletters | UPDATE | sender_email/sender_reply_to確認 |

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

#### newsletters

| 操作 | 項目（カラム名） | 更新値・取得条件 | 備考 |
|-----|-----------------|-----------------|------|
| UPDATE | sort_order | 新しい順序インデックス | 並び替え時 |
| UPDATE | sender_email | 検証済みメールアドレス | 検証時 |
| UPDATE | sender_reply_to | 検証済みReply-toアドレス | 検証時 |

## メッセージ仕様

| 種別 | メッセージ | 表示条件 |
|------|-----------|----------|
| 説明 | Edit details and customize your design | 常時表示（グループ説明） |
| 空表示 | No newsletters found. | 一覧が空の場合 |
| 検証成功 | Newsletter email verified | sender_email検証成功 |
| 検証成功 | Reply-to address verified | sender_reply_to検証成功 |
| 検証成功 | Email address verified | その他の検証成功 |
| 検証エラー | Error verifying email address | 検証失敗時 |
| 検証エラー | Verification link has expired. | トークン期限切れ |
| 検証エラー | There was an error verifying your email address. Try again later. | その他のエラー |
| もっと読み込む | Load more (showing X/Y newsletters) | isEnd=false時 |

## 例外処理

| 状態 | 処理内容 |
|------|----------|
| 読み込み中 | ローディング表示（Table isLoading） |
| ニュースレターなし | NoValueLabelコンポーネントで「No newsletters found.」表示 |
| 検証トークンエラー | ConfirmationModalでエラー内容を表示 |
| APIエラー | handleErrorでエラー処理（withToast: false） |

## 備考

- ニュースレターはuseBrowseNewslettersで無限スクロール対応
- アクティブなニュースレターのみ並び替え可能（SortableList使用）
- 並び替えは楽観的UIで即時反映、その後APIに保存
- アーカイブ済みニュースレターは並び替え不可
- 購読者数と配信数はnewsletter.countから取得
- 数値表示はnumberWithCommasでカンマ区切り
- 検証用URLパラメータはuseQueryParamsで取得
- NavigateToNewsletterコンポーネントでモーダルからのナビゲーション

---

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

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

### 推奨読解順序

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

まず、ニュースレターのデータ構造を理解することが重要である。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | newsletters.ts | `apps/admin-x-framework/src/api/newsletters.ts` | Newsletter型定義 |

**読解のコツ**: ニュースレターには`status`（active/archived）、`sort_order`、`count`（active_members, posts）など重要なプロパティがある。

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

処理の起点となるファイル・関数を特定する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | newsletters.tsx | `apps/admin-x-settings/src/components/settings/email/newsletters.tsx` | Newslettersコンポーネントの構造（24-169行目） |

**主要処理フロー**:
1. **25-36行目**: ルーティング、API、状態管理フックの初期化
2. **38-42行目**: APIデータをローカルステートに同期
3. **44-90行目**: メール検証処理
4. **92-96行目**: Add newsletterボタン
5. **98-99行目**: アクティブ/アーカイブのフィルタリング

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | newsletters-list.tsx | `apps/admin-x-settings/src/components/settings/email/newsletters/newsletters-list.tsx` | NewslettersListコンポーネント（81-107行目） |
| 3-2 | newsletters-list.tsx | 同上 | NewsletterItemコンポーネント（50-79行目） |

**主要処理フロー**:
- **50-79行目**: ニュースレター1件の表示（名前、説明、購読者数、配信数）
- **81-107行目**: 一覧の条件分岐（ローディング/ソート可能/通常/空）

#### Step 4: 並び替え処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | newsletters.tsx | `apps/admin-x-settings/src/components/settings/email/newsletters.tsx` | onSort関数（101-135行目） |

**主要処理フロー**:
- **101-104行目**: ドラッグ元/先のインデックス取得
- **106-108行目**: arrayMoveで新しい順序を計算
- **117行目**: ローカルステートを即時更新
- **118-130行目**: queryClientでキャッシュも更新
- **132-134行目**: APIに保存

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

```
Newsletters (newsletters.tsx)
    │
    ├─ useRouting()
    │      └─ updateRoute(route)
    │             ├─ 'newsletters/new' (新規作成)
    │             └─ 'newsletters/{id}' (編集)
    │
    ├─ useBrowseNewsletters()
    │      ├─ newsletters (データ)
    │      ├─ meta (ページネーション情報)
    │      ├─ isEnd (最終ページフラグ)
    │      ├─ isLoading
    │      └─ fetchNextPage()
    │
    ├─ useEditNewsletter()
    │      └─ mutateAsync(newsletter)
    │             └─ PUT /newsletters/{id}/
    │
    ├─ useVerifyNewsletterEmail()
    │      └─ mutateAsync({token})
    │             └─ PUT /newsletters/verifications/
    │
    ├─ useQueryParams()
    │      └─ getParam('verifyEmail')
    │
    ├─ useState
    │      ├─ selectedTab
    │      └─ newsletters (ローカルコピー)
    │
    ├─ useQueryClient()
    │      └─ setQueriesData() (キャッシュ更新)
    │
    └─ UI Components
           │
           ├─ TopLevelGroup
           │      └─ customButtons: Add newsletter
           │
           ├─ TabView
           │      ├─ Active tab → NewslettersList (isSortable)
           │      └─ Archived tab → NewslettersList
           │
           ├─ NewslettersList
           │      ├─ SortableList (アクティブ)
           │      │      └─ NewsletterItemContainer
           │      │             └─ NewsletterItem
           │      │
           │      └─ Table (アーカイブ/通常)
           │             └─ NewsletterItemContainer
           │                    └─ NewsletterItem
           │
           └─ Load more Button
                  └─ onClick: fetchNextPage()
```

### データフロー図

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

                    ┌─────────────────┐
API /newsletters/ ──┤useBrowseNewsletters│
                    │                   │
                    └────────┬──────────┘
                             │
                    ┌────────▼──────────┐
                    │useState(newsletters)│
                    │ローカルコピー        │
                    └────────┬──────────┘
                             │
              ┌──────────────┼──────────────┐
              │              │              │
    ┌─────────▼────┐  ┌──────▼─────┐       │
    │ filter       │  │ filter     │       │
    │ status=active│  │ status!=   │       │
    │              │  │ active     │       │
    └──────┬───────┘  └──────┬─────┘       │
           │                 │              │
    ┌──────▼───────┐  ┌──────▼─────┐       │
    │sortedActive  │  │archived    │       │
    │Newsletters   │  │Newsletters │       │
    └──────┬───────┘  └──────┬─────┘       │
           │                 │              │
           ├─────────────────┤              │
           │     TabView     │              │
           └────────┬────────┘              │
                    │                       │
    ドラッグ&ドロップ ─┘                      │
           │                                │
    ┌──────▼───────┐                        │
    │ onSort()     │                        │
    │ arrayMove    │                        │
    └──────┬───────┘                        │
           │                                │
    ┌──────▼───────┐                        │
    │setNewsletters│                        │
    │setQueriesData│                        │
    └──────┬───────┘                        │
           │                                │
    ┌──────▼───────┐                        │
    │editNewsletter│                        │
    │(sort_order)  │────────────────────────┘
    └──────────────┘
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| newsletters.tsx | `apps/admin-x-settings/src/components/settings/email/newsletters.tsx` | ソース | メインコンポーネント |
| newsletters-list.tsx | `apps/admin-x-settings/src/components/settings/email/newsletters/newsletters-list.tsx` | ソース | 一覧表示コンポーネント |
| newsletter-detail-modal.tsx | `apps/admin-x-settings/src/components/settings/email/newsletters/newsletter-detail-modal.tsx` | ソース | 詳細編集モーダル |
| newsletters.ts | `apps/admin-x-framework/src/api/newsletters.ts` | ソース | ニュースレターAPI定義 |
| top-level-group.tsx | `apps/admin-x-settings/src/components/top-level-group.tsx` | ソース | 設定グループUIコンポーネント |
| helpers.ts | `apps/admin-x-settings/src/utils/helpers.ts` | ソース | numberWithCommas等のユーティリティ |
