# 画面設計書 30-投稿ニュースレター分析画面

## 概要

投稿分析画面のNewsletterタブにおける詳細ニュースレター分析画面の設計書。特定の投稿がニュースレターとして配信された際のパフォーマンス（送信数、開封率、クリック率）を分析し、平均との比較、読者フィードバック、クリックされたリンクの詳細を可視化する。

### 本画面の処理概要

投稿ニュースレター分析画面は、個別投稿のメール配信パフォーマンスを多角的に分析するための専門画面である。

**業務上の目的・背景**：コンテンツ制作者がニュースレターとして配信した投稿のエンゲージメントを把握するために使用する。開封率・クリック率を過去の平均と比較することで、コンテンツの質を評価できる。また、どのリンクが最もクリックされたか、読者からのフィードバック（More like this / Less like this）を確認することで、コンテンツ戦略の改善に活用できる。

**画面へのアクセス方法**：投稿分析画面内の「Newsletter」タブをクリック、または直接 `/posts/analytics/:postId/newsletter` にアクセスする。ニュースレターとして配信されていない投稿の場合は概要画面にリダイレクトされる。

**主要な操作・処理内容**：
1. Sent/Opened/Clicked KPIカードの表示と「View members」クリックによるメンバー一覧遷移
2. 開封率・クリック率のラジアルチャート表示（平均との比較）
3. 読者フィードバックの確認（タブ切り替え）と「View all」によるメンバー一覧遷移
4. トップリンク一覧の確認・リンクURL編集・ページネーション

**画面遷移**：
- 遷移元：投稿分析概要画面（/posts/analytics/:postId）
- 遷移先：メンバー一覧画面（/members?filterParam=...）

**権限による表示制御**：
- emailTrackOpensEnabled無効時：Openedカードとラジアルチャートが非表示
- emailTrackClicksEnabled無効時：Clickedカードとラジアルチャート、トップリンク一覧が非表示
- shouldShowFeedback=false時：Feedbackカードが非表示

## 関連機能

| 機能No | 機能名 | 関連種別 | 関連する操作・処理 |
|--------|--------|----------|------------------|
| 22 | メール分析 | 主機能 | 投稿のニュースレター配信パフォーマンス分析 |
| 56 | オーディエンスフィードバック | 補助機能 | 読者からの評価データ表示 |

## 画面種別

ダッシュボード / 分析

## URL/ルーティング

| パス | コンポーネント | 説明 |
|------|---------------|------|
| `/posts/analytics/:postId/newsletter` | Newsletter | 投稿ニュースレター分析画面 |

## 入出力項目

### 入力項目

| 項目名 | 項目ID | データ型 | 必須 | 説明 |
|--------|--------|----------|------|------|
| 投稿ID | postId | string | Yes | URLパラメータ |

### 出力項目

| 項目名 | データ型 | 説明 |
|--------|----------|------|
| 送信数 | number | ニュースレターの送信数 |
| 開封数 | number | メールを開封したメンバー数 |
| クリック数 | number | リンクをクリックしたメンバー数 |
| 開封率 | number | 開封数 / 送信数 |
| クリック率 | number | クリック数 / 送信数 |
| 平均開封率 | number | 過去ニュースレターの平均開封率 |
| 平均クリック率 | number | 過去ニュースレターの平均クリック率 |
| ポジティブフィードバック数 | number | 「More like this」の数 |
| ネガティブフィードバック数 | number | 「Less like this」の数 |
| トップリンク | array | クリック数上位のリンク一覧 |

## 表示項目

### KPIカード

| 項目名 | カラー | 説明 | 表示条件 |
|--------|--------|------|---------|
| Sent | Purple | 送信数 | 常時表示 |
| Opened | Blue | 開封数 | emailTrackOpensEnabled |
| Clicked | Teal | クリック数 | emailTrackClicksEnabled |

### ラジアルチャート

| チャート名 | データ | 説明 | 表示条件 |
|-----------|--------|------|---------|
| Sent | value=1 | 100%固定表示 | 常時表示 |
| Opened | stats.openedRate, averageStats.openedRate | 開封率（平均との比較） | emailTrackOpensEnabled |
| Clicked | stats.clickedRate, averageStats.clickedRate | クリック率（平均との比較） | emailTrackClicksEnabled |

### フィードバックカード

| 項目名 | データ型 | 説明 |
|--------|----------|------|
| positiveFeedback | number | 「More like this」の数 |
| negativeFeedback | number | 「Less like this」の数 |
| totalFeedback | number | 合計フィードバック数 |

### トップリンクテーブル

| 項目名 | データ型 | 説明 |
|--------|----------|------|
| title | string | リンクのタイトル（リンクテキスト） |
| url | string | リンク先URL |
| count | number | クリック数 |
| percentage | number | 全クリック数に対する割合 |
| edited | boolean | URL編集済みフラグ |

## イベント仕様

### 1-「View members」クリック（Sent）

**トリガー**: Sent KPIカードの「View members」ボタンまたはラベルをクリック

**処理フロー**:
1. KpiCardMoreButton/KpiCardLabelのonClickが発火
2. filterParamを構築（`emails.post_id:${postId}`）
3. navigate関数で `/members?filterParam=...&postAnalytics=...` に遷移（crossApp: true）

**関連コード**: `apps/posts/src/views/PostAnalytics/Newsletter/newsletter.tsx` 285-303行目

### 2-「View members」クリック（Opened）

**トリガー**: Opened KPIカードの「View members」ボタンまたはラベルをクリック

**処理フロー**:
1. KpiCardMoreButton/KpiCardLabelのonClickが発火
2. filterParamを構築（`opened_emails.post_id:${postId}`）
3. navigate関数で `/members?filterParam=...&postAnalytics=...` に遷移（crossApp: true）

**関連コード**: `apps/posts/src/views/PostAnalytics/Newsletter/newsletter.tsx` 311-326行目

### 3-「View members」クリック（Clicked）

**トリガー**: Clicked KPIカードの「View members」ボタンまたはラベルをクリック

**処理フロー**:
1. KpiCardMoreButton/KpiCardLabelのonClickが発火
2. filterParamを構築（`clicked_links.post_id:${postId}`）
3. navigate関数で `/members?filterParam=...&postAnalytics=...` に遷移（crossApp: true）

**関連コード**: `apps/posts/src/views/PostAnalytics/Newsletter/newsletter.tsx` 338-353行目

### 4-フィードバックタブ切り替え

**トリガー**: Feedbackカード内のタブ（positive/negative）をクリック

**処理フロー**:
1. TabsTriggerのonValueChangeが発火
2. activeFeedbackTab状態が更新される
3. usePostFeedbackフックがscoreパラメータを変更して再取得
4. フィードバック一覧が再描画

**関連コード**: `apps/posts/src/views/PostAnalytics/Newsletter/components/feedback.tsx` 17, 21-22, 48行目

### 5-フィードバック「View all」クリック

**トリガー**: Feedbackカードの「View all」ボタンをクリック

**処理フロー**:
1. Buttonのonclickが発火
2. filterParamを構築（`(feedback.post_id:'${postId}'+feedback.score:1)` または `score:0`）
3. navigate関数で `/members?filter=...&post=...` に遷移（crossApp: true）

**関連コード**: `apps/posts/src/views/PostAnalytics/Newsletter/components/feedback.tsx` 109-116行目

### 6-フィードバックメンバークリック

**トリガー**: フィードバック一覧のメンバー行をクリック

**処理フロー**:
1. 行要素のonClickが発火
2. navigate関数で `/members/${item.member.id}` に遷移（crossApp: true）

**関連コード**: `apps/posts/src/views/PostAnalytics/Newsletter/components/feedback.tsx` 75-77行目

### 7-リンク編集ボタンクリック

**トリガー**: トップリンク一覧のペンアイコンボタンをクリック

**処理フロー**:
1. handleEdit関数が呼び出される
2. 対象リンクのIDをeditingLinkIdに設定
3. リンクURLをeditedUrlに設定
4. 入力フィールドが表示されフォーカスが当たる

**関連コード**: `apps/posts/src/views/PostAnalytics/Newsletter/newsletter.tsx` 130-136行目, 483行目

### 8-リンクURL更新

**トリガー**: リンク編集入力フィールドで「Update」ボタンをクリック

**処理フロー**:
1. handleUpdate関数が呼び出される
2. 空白チェック・変更有無チェック
3. editLinks関数でAPIを呼び出し
4. 成功時：状態をリセット、refetchTopLinksでデータ再取得
5. 入力フィールドが非表示になる

**関連コード**: `apps/posts/src/views/PostAnalytics/Newsletter/newsletter.tsx` 138-163行目, 472行目

### 9-トップリンクページネーション

**トリガー**: SimplePaginationPreviousButton/SimplePaginationNextButtonをクリック

**処理フロー**:
1. previousPage/nextPage関数が呼び出される
2. useSimplePaginationがpaginatedTopLinksを更新
3. テーブルが再描画

**関連コード**: `apps/posts/src/views/PostAnalytics/Newsletter/newsletter.tsx` 166-176, 529-538行目

### 10-非ニュースレター投稿時のリダイレクト

**トリガー**: 投稿ロード完了後、ニュースレター配信されていない投稿の場合

**処理フロー**:
1. useEffectで条件判定
2. showNewsletterSection（hasBeenEmailed）がfalse
3. navigate関数で `/posts/analytics/:postId` にリダイレクト

**関連コード**: `apps/posts/src/views/PostAnalytics/Newsletter/newsletter.tsx` 83-88行目

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

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

| 操作（イベント） | 対象テーブル | 操作種別 | 概要 |
|----------------|-------------|---------|------|
| 画面表示 | posts, emails | SELECT | 投稿情報とメール統計取得 |
| 画面表示 | links | SELECT | トップリンク取得（useTopLinks） |
| 画面表示 | posts_feedback | SELECT | フィードバックデータ取得 |
| リンクURL更新 | links | UPDATE | リンクURLの編集（editLinks） |

### Ghost API呼び出し詳細

| エンドポイント | パラメータ | 用途 |
|---------------|-----------|------|
| /ghost/api/admin/posts/:postId | - | 投稿情報取得（getPost） |
| /ghost/api/admin/posts/:postId | include=count.positive_feedback,count.negative_feedback | フィードバック数取得 |
| /ghost/api/admin/stats/newsletter/basic | newsletter_id | ニュースレター基本統計取得 |
| /ghost/api/admin/stats/newsletter/clicks | newsletter_id, post_ids | ニュースレタークリック統計取得 |
| /ghost/api/admin/links/top | filter=post_id:'...' | トップリンク取得 |
| /ghost/api/admin/posts/feedback | post_id, score | フィードバック詳細取得 |
| /ghost/api/admin/links | originalUrl, editedUrl, postId | リンクURL更新 |

## メッセージ仕様

| メッセージID | 種別 | メッセージ内容 | 表示条件 |
|-------------|------|---------------|---------|
| - | 説明 | What did your readers think? | Feedbackカード説明文 |
| - | 説明 | Which links resonated with your readers | トップリンクカード説明文 |
| - | 情報 | You have no links in your post. | リンクがない場合 |
| - | 情報 | No members have given feedback yet | フィードバックなし |
| - | 情報 | No positive/negative feedback yet | タブ選択時データなし |
| - | ヒント | Sent a broken link? You can update it! | トップリンクカードフッター |

## 例外処理

| 例外条件 | 処理内容 |
|---------|---------|
| 非ニュースレター投稿 | 概要画面にリダイレクト |
| ローディング中 | BarChartLoadingIndicator/SkeletonTable表示 |
| フィードバックなし | 空状態メッセージ表示 |
| リンクなし | 空状態メッセージ表示 |

## 備考

- NewsletterRadialChartはRechartsのRadialBarChartを使用
- 平均値は同じニュースレターの過去20件から算出
- chartHeaderClassはトラッキング設定により動的に変更（grid-cols-1/2/3）
- フィードバックはポジティブ/ネガティブをタブで切り替え
- リンク編集時はクリック外部検知でキャンセル可能
- トップリンクは10件ごとにページネーション

---

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

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

### 推奨読解順序

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | newsletter.tsx | `apps/posts/src/views/PostAnalytics/Newsletter/newsletter.tsx` | NewsletterRadialChartData型（6行目import） |
| 1-2 | use-post-newsletter-stats.ts | `apps/posts/src/hooks/use-post-newsletter-stats.ts` | stats, averageStats, topLinks, feedbackStatsの構造（29-47, 167-172行目） |
| 1-3 | feedback.tsx | `apps/posts/src/views/PostAnalytics/Newsletter/components/feedback.tsx` | FeedbackProps型（6-12行目） |

**読解のコツ**: statsにはsent, opened, clicked, openedRate, clickedRateが含まれる。averageStatsはニュースレター平均値。

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | newsletter.tsx | `apps/posts/src/views/PostAnalytics/Newsletter/newsletter.tsx` | Newsletterコンポーネント全体（66-553行目） |
| 2-2 | routes.tsx | `apps/posts/src/routes.tsx` | /newsletterルート定義（49-52行目） |

**主要処理フロー**:
1. **67-75行目**: 各種フック・状態初期化（navigate, editingLinkId, inputRef, containerRef, ITEMS_PER_PAGE, chartSize, appSettings）
2. **78行目**: useGlobalDataでpost/isPostLoading/postId取得
3. **81行目**: hasBeenEmailedでニュースレター配信済みか判定
4. **83-88行目**: 非ニュースレター投稿時のリダイレクト
5. **90-91行目**: usePostNewsletterStatsでstats/averageStats/topLinks/isLoading取得
6. **94-112行目**: feedbackStatsの計算（useMemo）
7. **115-128行目**: shouldShowFeedbackの判定
8. **130-163行目**: リンク編集ハンドラー（handleEdit, handleUpdate）
9. **166-176行目**: トップリンクのページネーション
10. **202-252行目**: チャートデータ構築（sent/opened/clicked）
11. **254-264行目**: chartHeaderClass/chartClassの条件分岐
12. **266-549行目**: JSX描画

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

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | newsletter-radial-chart.tsx | `apps/posts/src/views/PostAnalytics/Newsletter/components/newsletter-radial-chart.tsx` | ラジアルチャート表示（20-185行目） |
| 3-2 | feedback.tsx | `apps/posts/src/views/PostAnalytics/Newsletter/components/feedback.tsx` | フィードバック表示・タブ切り替え（14-141行目） |
| 3-3 | kpi-card.tsx | `apps/posts/src/views/PostAnalytics/components/kpi-card.tsx` | KPIカードコンポーネント |

#### Step 4: カスタムフックを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | use-post-newsletter-stats.ts | `apps/posts/src/hooks/use-post-newsletter-stats.ts` | ニュースレター統計取得（14-183行目） |
| 4-2 | use-post-feedback.ts | `apps/posts/src/hooks/use-post-feedback.ts` | フィードバック詳細取得 |
| 4-3 | use-edit-links.ts | `apps/posts/src/hooks/use-edit-links.ts` | リンクURL編集 |

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

```
Newsletter (newsletter.tsx)
    |
    +-- useNavigate()
    |      +-- navigate関数
    |
    +-- useState x 2
    |      +-- editingLinkId, editedUrl
    |
    +-- useRef x 2
    |      +-- inputRef, containerRef
    |
    +-- useResponsiveChartSize()
    |      +-- chartSize
    |
    +-- useAppContext()
    |      +-- appSettings (emailTrackClicks, emailTrackOpens)
    |
    +-- useGlobalData()
    |      +-- post, isPostLoading, postId
    |
    +-- hasBeenEmailed(post)
    |      +-- showNewsletterSection
    |
    +-- usePostNewsletterStats(postId)
    |      +-- stats (sent/opened/clicked/rate)
    |      +-- averageStats (openedRate/clickedRate)
    |      +-- topLinks
    |      +-- isLoading
    |      +-- refetchTopLinks
    |
    +-- useEditLinks()
    |      +-- editLinks
    |
    +-- useMemo: feedbackStats
    |      +-- positiveFeedback/negativeFeedback/totalFeedback
    |
    +-- useMemo: shouldShowFeedback
    |
    +-- useSimplePagination(topLinks)
    |      +-- paginatedTopLinks, totalPages, nextPage, previousPage
    |
    +-- KpiCard x 3 (Sent/Opened/Clicked)
    |      +-- KpiCardMoreButton -> navigate('/members')
    |      +-- KpiCardLabel -> navigate('/members')
    |      +-- KpiCardValue
    |
    +-- NewsletterRadialChart x 3
    |      +-- Sent (100%固定)
    |      +-- Opened (平均比較)
    |      +-- Clicked (平均比較)
    |
    +-- Feedback
    |      +-- Tabs (positive/negative)
    |      +-- usePostFeedback
    |      +-- useSimplePagination
    |      +-- "View all" -> navigate('/members')
    |
    +-- TopLinks Card
           +-- DataList + DataListRow
           +-- リンク編集 (Input + Button)
           +-- SimplePagination
```

### データフロー図

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

postId (URLパラメータ) ---------> useGlobalData -----------------> post, isPostLoading
                                        |
                                        v
                                 hasBeenEmailed(post)
                                        |
                                        v
                                 showNewsletterSection? ----no----> リダイレクト
                                        |yes
                                        v
postId --------------------------> usePostNewsletterStats
                                        |
                    +-------------------+-------------------+
                    |                   |                   |
                    v                   v                   v
                  stats            averageStats          topLinks
                    |                   |                   |
                    v                   v                   v
              KpiCard x 3      NewsletterRadialChart   DataList
                    |                   |                   |
                    v                   v                   v
            送信/開封/クリック数   開封率/クリック率      リンク一覧
                                   (平均比較)

feedbackStats -------------------> Feedback Card
(from post.count)                      |
                                       v
                              usePostFeedback(score)
                                       |
                                       v
                              フィードバック一覧

「View members」クリック -----------> filterParam構築 ---------> navigate('/members')
                                        |
                                 URLSearchParams

リンク編集 --------------------------> editLinks API -----------> refetchTopLinks
                                        |
                                 originalUrl, editedUrl
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| newsletter.tsx | `apps/posts/src/views/PostAnalytics/Newsletter/newsletter.tsx` | ソース | ニュースレター分析画面メイン |
| newsletter-radial-chart.tsx | `apps/posts/src/views/PostAnalytics/Newsletter/components/newsletter-radial-chart.tsx` | ソース | ラジアルチャートコンポーネント |
| feedback.tsx | `apps/posts/src/views/PostAnalytics/Newsletter/components/feedback.tsx` | ソース | フィードバックカード |
| kpi-card.tsx | `apps/posts/src/views/PostAnalytics/components/kpi-card.tsx` | ソース | KPIカードコンポーネント |
| post-analytics-header.tsx | `apps/posts/src/views/PostAnalytics/components/post-analytics-header.tsx` | ソース | ヘッダー |
| post-analytics-content.tsx | `apps/posts/src/views/PostAnalytics/components/post-analytics-content.tsx` | ソース | コンテンツエリア |
| use-post-newsletter-stats.ts | `apps/posts/src/hooks/use-post-newsletter-stats.ts` | ソース | ニュースレター統計フック |
| use-post-feedback.ts | `apps/posts/src/hooks/use-post-feedback.ts` | ソース | フィードバック取得フック |
| use-edit-links.ts | `apps/posts/src/hooks/use-edit-links.ts` | ソース | リンク編集フック |
| use-responsive-chart-size.ts | `apps/posts/src/hooks/use-responsive-chart-size.ts` | ソース | チャートサイズフック |
| link-helpers.ts | `apps/posts/src/utils/link-helpers.ts` | ソース | リンクヘルパー関数 |
