コンテンツにスキップ

Design

Context

現在の MemberDetailPage は、メンバーの全セッション出席履歴をフラットなテーブルで表示している。データモデル上、各セッションは studyGroupId を持ち、index.jsonstudyGroups 配列で勉強会名を逆引きできるため、勉強会別のグルーピングに必要な情報はすでに揃っている。

現状のデータ取得フローは以下のとおり:

  1. index.json を取得してメンバーの sessionIds を取得
  2. 各セッションJSONを並列取得
  3. 該当メンバーの出席記録を抽出し、日付降順でフラット表示

このフローを活かしつつ、取得したセッションデータを studyGroupId でグルーピングする処理を追加する。

Goals / Non-Goals

Goals:

  • メンバー詳細ページで勉強会別のサマリー(勉強会名、参加回数、合計時間)をカード形式で表示する
  • 勉強会カードをクリックすると、その勉強会に紐づくセッション出席履歴を展開表示する
  • 既存のデータ取得ロジック(DataFetcher)を変更せずにUI側のみで対応する

Non-Goals:

  • DataFetcherやデータモデル(index.json, sessions/*.json)の変更
  • ルーティング構造の変更(URLは /members/:memberId のまま)
  • 新しいページの追加(既存の MemberDetailPage 内で完結)
  • 勉強会詳細ページへの遷移機能

Decisions

1. 勉強会別グルーピングはコンポーネント内で処理する

選択: セッションデータ取得後、MemberDetailPage のuseEffect内で studyGroupId をキーにグルーピングし、state に保持する。

理由: グルーピングは表示のためだけの変換処理であり、DataFetcherやサービス層に責務を持たせる必要はない。既存のデータ取得フローに最小限の変更で対応できる。

代替案: DataFetcherに勉強会別取得メソッドを追加する → データ取得の責務を超えるため不採用。

2. アコーディオン方式で勉強会ごとの出席履歴を展開する

選択: 勉強会サマリーカードをクリックすると、そのカードの下にセッション一覧がアコーディオン展開される。

理由: ページ遷移なしで詳細を確認でき、複数の勉強会を比較しやすい。React のローカル state(useState)だけで実装可能。

代替案:

  • 勉強会選択で別セクションに表示 → 画面スクロールが発生し直感的でない
  • 勉強会ごとに別ページ → 過剰なルーティング変更となり不要

3. 既存コンポーネントの再利用

選択: ヘッダーカード部分(メンバー名、合計時間、参加回数)は現在のデザインを維持する。勉強会サマリーカードは新規作成するが、プロジェクトの既存デザイントークン(bg-surface, rounded-xl, border-border-light など)を踏襲する。

理由: UIの一貫性を保つため。SummaryCardコンポーネントは用途が異なる(統計値表示用)ため、勉強会サマリーには専用のレイアウトを使う。

4. state構造

選択: 既存の attendanceList(フラット配列)を studyGroupAttendances(勉強会別にグルーピングしたMap/オブジェクト)に置き換える。各エントリは勉強会ID、勉強会名、参加回数、合計時間、セッション一覧を持つ。

studyGroupAttendances: [
  {
    studyGroupId: string,
    studyGroupName: string,
    sessionCount: number,
    totalDurationSeconds: number,
    sessions: [{ date, durationSeconds }]  // 日付降順
  }
]

理由: サマリー表示と詳細表示の両方に必要な情報を1つの構造で保持でき、追加のデータ取得が不要。

Risks / Trade-offs

  • 勉強会が1つしかない場合のUX: 現状のデータでは勉強会は「もくもく勉強会」のみ。勉強会が1つの場合でも2層構造にすることで、以前よりワンクリック多くなる → 勉強会が1つの場合はデフォルトで展開状態にすることで緩和する。
  • セッション数の増加によるパフォーマンス: 全セッションを並列取得する現在のアプローチはセッション数が増えると負荷が高まる → 現時点ではセッション数が少ないため問題なし。将来的にはページネーションやlazyロードが必要になる可能性があるが、今回のスコープ外とする。