## はじめに
第五弾(独自スコアリング分析システム)は、バックエンドだけならすでに完成していました。残りピンをビットマスクで構造化し、ストライク・スペア・タップ・スプリット・オープンなどにフレームを分類する仕組みや、ギア別・レーン別の成績分析まで、E2Eテスト77件がすべてパスした状態です。
でも、それを「見る画面」がありませんでした。この記事は、そこにフロントエンドを組み込んだ記録です。
## なぜReactを選んだのか
これまでのプロジェクト(第一弾・第二弾)はStreamlitでフロントエンドを作ってきました。今回、全プロジェクトをStreamlitに統一するのではなく、1プロジェクトだけあえてReactに挑戦する方針を採りました。
正直、これは「今すぐ建物に導入できる統合された状態」からは遠ざかる判断です。それでも、転職市場でのReact需要を優先し、割り切った投資として受け入れることにしました。
Reactで作る対象に第五弾を選んだのは、データ可視化でReactの強みを一番発揮できる位置づけだと考えたからです。
## 会員向け画面から着手
まず着手したのは会員向けのマイページです。
- Vite + React環境構築(Node.js v24.21.0を新規インストール)
- ダミーデータ版のダッシュボード画面をまず完成させる
- 画面構成:サマリーカード4つ+指標カード5つ+フレーム内訳の円グラフ(Recharts)+ギア別成績表+直近5ゲームリスト
使用するAPIは以下の3つです。
```
GET /api/v1/dashboard/member/{user_id} … サマリー・投球傾向指標
GET /api/v1/dashboard/member/{user_id}/gear-performance … ギア別成績
GET /api/v1/dashboard/member/{user_id}/loss-factors … フレーム内訳
```
ダミーデータは実際のレスポンス形に合わせて作っておき、本番切り替え時はaxios呼び出しに差し替えるだけで動くように設計しました。プロジェクトフォルダはバックエンド(bowling-lane-scoring-system)とは分け、bowling-lane-scoring-frontendとして新規に作成しています。
## 本物のAPIとつなぐ
ダミーデータでの画面が固まったところで、本物のバックエンドAPIとの接続に進みました。
- app/main.pyにCORSMiddlewareを追加(allow_origins=localhost:5173)
- テスト会員(member_code=M001)・テストゲーム1件(スコア162、ストライク5・スペア1・オープン3)を実データとして投入
- React画面がAPIレスポンスと完全一致する表示になることを確認
認証は暫定でStaffトークンを使い、会員本人のログイン画面は次のステップに回しました。
## スタッフ向け画面とログイン機能
続いてスタッフ向けの画面です。バックエンド側にはすでに3つのエンドポイントが実装済みでした。
```
GET /api/v1/dashboard/lanes/performance … レーン別成績
GET /api/v1/dashboard/conditions/performance … コンディション(オイルパターン)別成績
GET /api/v1/dashboard/center/overview … センター全体サマリー
```
conditions/performanceは、オイルパターンをレーン番号+適用日(applied_date <= session.start_time)で逆引きするロジックになっていて、これを使ってスタッフ向けダッシュボード(StaffDashboard.jsx)を実装。センターサマリー・レーン別・コンディション別をRechartsの棒グラフで表示し、実データでの接続確認まで済ませました。
会員本人のログイン画面も実装しました。POST /api/v1/users/loginに識別子(identifier)とPINコードを送り、成功したらトークンをlocalStorageに保存する方式です。ログアウトボタンも用意しています。
## 実機データを受け取る入口を作る
最後に取り組んだのが、実機からの生データ取込です。汎用的なCSVフォーマットを新しく設計しました。
```
1行 = 1投
列: lane_number / player_slot / game_number / frame_number
/ shot_number / pins_knocked / remaining_pin_numbers / timestamp
```
これをPOST /api/v1/scores/import-csvとして実装。レーン番号とタイムスタンプから、既存のBowlingSession→SessionPlayerを逆引きして投球者を自動特定するロジックにしています。マッチしなかった行はunmatchedリストとして報告される仕組みなので、取込ミスにも気づける設計です。
## おわりに
会員向け画面、スタッフ向け画面、会員本人ログイン、実機CSV取込 ― これで第五弾のフロントエンドは一通り完了しました。
バックエンドを積み上げてきたこのシリーズの中で、初めて「見て触れる画面」ができた回でもあります。データが数字の羅列から、グラフやカードとして目に見える形になったとき、あらためてこのシステムが「動いている」実感がありました。
スポーツボウリング場構想・第五弾フロントエンド編は、これで一区切りです。🎳