Skip to content

feat: 成績表示ページの実装 (Frontend)#63

Merged
kdix-23-071 merged 1 commit intodevelopfrom
feature/issue-62-grades-display
Feb 20, 2026
Merged

feat: 成績表示ページの実装 (Frontend)#63
kdix-23-071 merged 1 commit intodevelopfrom
feature/issue-62-grades-display

Conversation

@kdix-23-071
Copy link
Contributor

実装の概要

  • 成績表示ページ(Grades)のUI実装
  • 連続記録・修了した問題数を表示する StatusCard コンポーネントの作成
  • 獲得バッジ一覧を表示する BadgeList コンポーネントの作成
  • ダミーデータを使用した表示確認

🔧 技術的な意思決定とトレードオフ (最重要)

採用したアプローチ

  • 手法: フロントエンド単体での実装先行のため、mock.ts にダミーデータを定義して使用
  • メリット: バックエンドの実装を待たずにUIの確認・調整が可能
  • デメリット/リスク: 後ほど実際のAPIレスポンスに合わせて型定義やデータ取得ロジックの修正が必要になる可能性がある

却下したアプローチ(代替案)

  • 手法: 最初からAPIクライアントを実装する
  • 却下理由: API仕様が未確定またはバックエンドが未実装のため

🧪 テスト戦略と範囲

追加したテストケース

  • 正常系: ダミーデータが正しく表示されること
  • 正常系: レスポンシブ表示(画面幅変更時のレイアウト崩れがないか)
  • 異常系: データが空の場合の表示(今回は未検証)
  • テストしていないこと: 実際のAPIとの通信

セキュリティに関する自己評価

  • 機密情報のハードコードはないか
  • 入力値の検証(バリデーション)は行っているか
  • 既知の脆弱性パターンへの対策は考慮したか

レビュワー(人間)への申し送り事項

  • 現状は mock.ts のデータを使用しています。
  • デザイン調整のため、StatusCard.tsx のマージン等を細かく調整しています。

@kdix-23-071
Copy link
Contributor Author

サイドバーが正しくないのは無視してください
あとで別issueに切り出して修正します
Screenshot 2026-02-20 at 15 14 34

@kdix-23-071 kdix-23-071 merged commit 85d4493 into develop Feb 20, 2026
6 checks passed
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

このPRは、成績表示ページ(Grades)のフロントエンド実装を追加するものです。バックエンドAPIの実装を待たずにUI開発を進めるため、モックデータを使用したアプローチを採用しています。

Changes:

  • 成績統計(連続記録・完了問題数)を表示する StatusCard コンポーネントの追加
  • 獲得バッジ一覧を表示する BadgeList コンポーネントの追加
  • データ取得とコンポーネント統合を行う GradesContainer の実装
  • モックAPIとTypeScript型定義の追加
  • /grades ページとレイアウトの実装

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
frontend/src/features/grades/types/index.ts 成績統計とバッジの型定義を追加
frontend/src/features/grades/components/StatusCard.tsx 連続記録と完了問題数を表示するカードコンポーネント
frontend/src/features/grades/components/BadgeList.tsx 獲得バッジを表示するリストコンポーネント
frontend/src/features/grades/components/GradesContainer.tsx データフェッチとコンポーネント統合を行うコンテナ
frontend/src/features/grades/api/mock.ts 開発用のモックAPIを実装
frontend/src/app/grades/page.tsx 成績ページのエントリーポイント
frontend/src/app/grades/layout.tsx 成績ページのレイアウト(ヘッダー・サイドバー含む)
Comments suppressed due to low confidence (11)

frontend/src/features/grades/types/index.ts:1

  • 型定義ファイルにJSDocコメントがありません。既存のコードベースでは、型定義ファイルにもJSDocコメントが付けられています。

例:

/**
 * Dashboard Feature Types
 * ダッシュボード機能固有の型定義
 */

参考: frontend/src/features/dashboard/types/index.ts:1-4

export interface GradeStats {

frontend/src/features/grades/components/BadgeList.tsx:14

  • 「バッチ」は誤字です。正しくは「バッジ」です。
        <h2 className="text-xl font-medium text-[#1a4023]">取得したバッチ</h2>

frontend/src/app/grades/layout.tsx:31

  • grades/layout.tsx のメインコンテンツエリアのクラスが dashboard/layout.tsx と異なっています。
  • dashboard: flex-1 p-8 sm:ml-64
  • grades: flex-1 min-h-[calc(100vh-4rem)] bg-[#FDFEF0] sm:ml-64

レイアウトの一貫性のため、同じスタイルを適用するか、意図的な違いであればコメントで理由を記載することを推奨します。

         <div className="flex-1 min-h-[calc(100vh-4rem)] bg-[#FDFEF0] sm:ml-64">
            {children}
         </div>

frontend/src/features/grades/components/BadgeList.tsx:10

  • コンポーネントにJSDocコメントがありません。既存のコードベースでは、すべてのコンポーネントにJSDocコメントが付けられています。

例:

/**
 * BadgeList Component
 * 獲得したバッジ一覧を表示
 */

参考: frontend/src/features/dashboard/components/BadgeList.tsx:3-6

import React from 'react';
import { Badge } from '../types';

interface BadgeListProps {
  badges: Badge[];
}

export const BadgeList: React.FC<BadgeListProps> = ({ badges }) => {

frontend/src/features/grades/components/StatusCard.tsx:10

  • コンポーネントにJSDocコメントがありません。既存のコードベースでは、すべてのコンポーネントにJSDocコメントが付けられています。

例:

/**
 * StatusCard Component
 * 連続記録と修了した問題数を表示
 */

参考: frontend/src/features/dashboard/components/StatusCard.tsx:3-6

import React from 'react';
import { GradeStats } from '../types';

interface StatusCardProps {
  stats: GradeStats;
}

export const StatusCard: React.FC<StatusCardProps> = ({ stats }) => {

frontend/src/features/grades/components/GradesContainer.tsx:9

  • コンポーネントにJSDocコメントがありません。既存のコードベースでは、すべてのコンポーネントにJSDocコメントが付けられています。

例:

/**
 * GradesContainer Component
 * 成績表示の全コンポーネントを統合し、データフェッチを担当
 */

参考: frontend/src/features/dashboard/components/DashboardContainer.tsx:3-6

import React, { useEffect, useState } from 'react';
import { StatusCard } from './StatusCard';
import { BadgeList } from './BadgeList';
import { getGradeStats, getBadges } from '../api/mock';
import type { GradeStats, Badge } from '../types';

export const GradesContainer: React.FC = () => {

frontend/src/features/grades/components/StatusCard.tsx:10

  • コンポーネントのエクスポート方法がコードベースの既存パターンと異なります。
  • 既存(dashboard): export function ComponentName を使用
  • 新規(grades): export const ComponentName: React.FC を使用

コードベースの一貫性のため、既存のパターンに合わせることを推奨します。

参考: frontend/src/features/dashboard/components/StatusCard.tsx:14

export const StatusCard: React.FC<StatusCardProps> = ({ stats }) => {

frontend/src/features/grades/components/BadgeList.tsx:10

  • コンポーネントのエクスポート方法がコードベースの既存パターンと異なります。
  • 既存(dashboard): export function ComponentName を使用
  • 新規(grades): export const ComponentName: React.FC を使用

コードベースの一貫性のため、既存のパターンに合わせることを推奨します。

参考: frontend/src/features/dashboard/components/BadgeList.tsx:14

export const BadgeList: React.FC<BadgeListProps> = ({ badges }) => {

frontend/src/features/grades/api/mock.ts:9

  • ファイルとエクスポート関数にJSDocコメントがありません。既存のコードベースでは、APIモジュールにもJSDocコメントが付けられています。

例:

/**
 * Dashboard Feature Mock Data
 * この機能専用のモックデータを返す関数
 */

参考: frontend/src/features/dashboard/api/mock.ts:1-4, 77-79

import { Badge, GradeStats } from "../types";

export const getGradeStats = async (): Promise<GradeStats> => {
  // TODO: Replace with actual API call
  return {
    consecutiveDays: 365,
    completedQuests: 365
  };
};

frontend/src/app/grades/layout.tsx:22

  • ユーザーアイコンの実装がdashboard/layout.tsxと異なっています。
  • dashboard: シンプルな絵文字(👤)を使用
  • grades: SVGアイコンとhover効果、cursor-pointerを使用

一貫性のため、同じ実装を使用することを推奨します。

         <div className="flex h-10 w-10 items-center justify-center rounded-full bg-white text-gray-600 shadow-sm cursor-pointer hover:bg-gray-100 transition-colors">
            {/* User Icon Placeholder */}
            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" className="w-6 h-6 text-gray-400">
              <path fillRule="evenodd" d="M7.5 6a4.5 4.5 0 119 0 4.5 4.5 0 01-9 0zM3.751 20.105a8.25 8.25 0 0116.498 0 .75.75 0 01-.437.695A18.683 18.683 0 0112 22.5c-2.786 0-5.433-.608-7.812-1.7a.75.75 0 01-.437-.695z" clipRule="evenodd" />
            </svg>
         </div>

frontend/src/features/grades/components/GradesContainer.tsx:9

  • コンポーネントのエクスポート方法がコードベースの既存パターンと異なります。
  • 既存(dashboard): export function ComponentName を使用
  • 新規(grades): export const ComponentName: React.FC を使用

コードベースの一貫性のため、既存のパターンに合わせることを推奨します。

参考: frontend/src/features/dashboard/components/DashboardContainer.tsx:17

export const GradesContainer: React.FC = () => {

</div>

{/* Completed Quests */}
<div className="flex flex-col items-left min-w-[200px] relative">
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tailwind CSSのクラス名が誤っています。items-left は存在しないクラスです。左寄せの場合は items-start を使用してください。

Copilot uses AI. Check for mistakes.
Comment on lines +6 to +11
export interface Badge {
id: string;
name: string;
icon: string;
type: 'trophy' | 'gold' | 'silver' | 'bronze';
earnedAt?: string;
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Badge型の定義がdashboard機能の既存のBadge型と異なっています。dashboard機能では description: stringunlockedAt?: Date を使用していますが、この実装では type: 'trophy' | 'gold' | 'silver' | 'bronze'earnedAt?: string を使用しています。

型の一貫性を保つため、以下のいずれかを検討してください:

  1. 共通のBadge型を作成して両方の機能で使用する
  2. 異なる目的の場合は、型名を区別する(例: GradeBadge vs DashboardBadge
  3. 既存のBadge型を拡張する

また、earnedAt フィールドは既存コードでは Date 型ですが、ここでは string 型になっています。日付の扱いを統一することをお勧めします。

Suggested change
export interface Badge {
id: string;
name: string;
icon: string;
type: 'trophy' | 'gold' | 'silver' | 'bronze';
earnedAt?: string;
export interface GradeBadge {
id: string;
name: string;
icon: string;
type: 'trophy' | 'gold' | 'silver' | 'bronze';
earnedAt?: Date;

Copilot uses AI. Check for mistakes.
Comment on lines +15 to +26
<div className="relative">
<select className="text-gray-700 appearance-none bg-white border border-border-[#1a4023] rounded px-4 py-1 pr-8 text-sm focus:outline-none ">
<option>全て</option>
<option>座学</option>
<option>実技</option>
</select>
<div className="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-gray-700">
<svg className="fill-current h-4 w-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
<path d="M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z"/>
</svg>
</div>
</div>
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

selectドロップダウンが実装されていますが、onChange ハンドラーがなく、選択時のフィルタリング機能が動作しません。

フィルタリング機能を実装するか、現時点で不要であればUIから削除することを検討してください。

Copilot uses AI. Check for mistakes.
return (
<div className="flex justify-around items-start w-full max-w-5xl mx-auto px-6 relative mt-16 pt-10">
{/* Consecutive Days */}
<div className="flex flex-col items-left min-w-[200px] relative">
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tailwind CSSのクラス名が誤っています。items-left は存在しないクラスです。左寄せの場合は items-start を使用してください。

Copilot uses AI. Check for mistakes.
Comment on lines +16 to +20
<select className="text-gray-700 appearance-none bg-white border border-border-[#1a4023] rounded px-4 py-1 pr-8 text-sm focus:outline-none ">
<option>全て</option>
<option>座学</option>
<option>実技</option>
</select>
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

selectドロップダウンにアクセシビリティのためのaria-labelや対応する<label>要素が設定されていません。スクリーンリーダーを使用するユーザーのために、適切なラベルを追加してください。

Copilot uses AI. Check for mistakes.
Comment on lines +23 to +27
} catch (error) {
console.error('Failed to fetch grade data:', error);
} finally {
setLoading(false);
}
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

エラーが発生した場合、console.errorでログ出力するのみで、ユーザーへのエラーメッセージ表示がありません。エラー状態を管理し、ユーザーにわかりやすいエラーメッセージを表示することを推奨します。

参考: dashboard/components/DashboardContainer.tsx では error state を管理して適切なエラー表示を行っています。

Copilot generated this review using guidance from repository custom instructions.
Comment on lines +10 to +63
export const BadgeList: React.FC<BadgeListProps> = ({ badges }) => {
return (
<div className="w-full max-w-5xl mx-auto px-4">
<div className="flex items-center gap-4 mb-4 pl-2">
<h2 className="text-xl font-medium text-[#1a4023]">取得したバッチ</h2>
<div className="relative">
<select className="text-gray-700 appearance-none bg-white border border-border-[#1a4023] rounded px-4 py-1 pr-8 text-sm focus:outline-none ">
<option>全て</option>
<option>座学</option>
<option>実技</option>
</select>
<div className="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-gray-700">
<svg className="fill-current h-4 w-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
<path d="M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z"/>
</svg>
</div>
</div>
</div>

<div className="border-2 border-[#1a4023] rounded-[40px] bg-[#f8fff8] p-8 lg:p-12 min-h-[250px] lg:min-h-[300px] flex items-center justify-around relative overflow-hidden">
{/* Large Trophy on the left */}
<div className="flex flex-col items-center justify-center transform scale-125 lg:scale-150">
<span className="text-[100px] lg:text-[120px] drop-shadow-lg filter">🏆</span>
</div>

{/* Medals on the right */}
<div className="flex gap-8 lg:gap-12 items-end">
{/* Gold - 1st */}
<div className="flex flex-col items-center">
<div className="relative">
<span className="text-[60px] lg:text-[80px] drop-shadow-md">🥇</span>

</div>
</div>

{/* Silver - 2nd */}
<div className="flex flex-col items-center">
<div className="relative">
<span className="text-[60px] lg:text-[80px] drop-shadow-md">🥈</span>

</div>
</div>

{/* Bronze - 3rd */}
<div className="flex flex-col items-center">
<div className="relative">
<span className="text-[60px] lg:text-[80px] drop-shadow-md">🥉</span>

</div>
</div>
</div>
</div>
</div>
);
Copy link

Copilot AI Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BadgeListコンポーネントは badges プロパティを受け取っていますが、実際には使用されていません。コンポーネント内では固定のバッジアイコン(🏆🥇🥈🥉)のみが表示されており、動的なデータが反映されていません。

badges プロパティのデータを実際に使用して動的に表示するか、現時点で必要ない場合はプロパティを削除することを検討してください。

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants