Design
Context¶
現在の MemberDetailPage は、メンバーの全セッション出席履歴をフラットなテーブルで表示している。データモデル上、各セッションは studyGroupId を持ち、index.json の studyGroups 配列で勉強会名を逆引きできるため、勉強会別のグルーピングに必要な情報はすでに揃っている。
現状のデータ取得フローは以下のとおり:
index.jsonを取得してメンバーのsessionIdsを取得- 各セッションJSONを並列取得
- 該当メンバーの出席記録を抽出し、日付降順でフラット表示
このフローを活かしつつ、取得したセッションデータを 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ロードが必要になる可能性があるが、今回のスコープ外とする。