第五弾フロントエンド編 ― データ可視化のためにReactに挑戦した記録

第五弾フロントエンド編 ― データ可視化のためにReactに挑戦した記録

記事
IT・テクノロジー
## はじめに

第五弾(独自スコアリング分析システム)は、バックエンドだけならすでに完成していました。残りピンをビットマスクで構造化し、ストライク・スペア・タップ・スプリット・オープンなどにフレームを分類する仕組みや、ギア別・レーン別の成績分析まで、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取込 ― これで第五弾のフロントエンドは一通り完了しました。

バックエンドを積み上げてきたこのシリーズの中で、初めて「見て触れる画面」ができた回でもあります。データが数字の羅列から、グラフやカードとして目に見える形になったとき、あらためてこのシステムが「動いている」実感がありました。

スポーツボウリング場構想・第五弾フロントエンド編は、これで一区切りです。🎳
サービス数100万件のスキルマーケット、あなたにぴったりのサービスを探す