Skip to content

feat(frontend): 実習メニュー画面の更新とSkillTreeのロールバック#55

Merged
Inlet-back merged 7 commits intodevelopfrom
feature/issue-48-exercise-menu
Feb 20, 2026
Merged

feat(frontend): 実習メニュー画面の更新とSkillTreeのロールバック#55
Inlet-back merged 7 commits intodevelopfrom
feature/issue-48-exercise-menu

Conversation

@kdix-23-071
Copy link
Contributor

@kdix-23-071 kdix-23-071 commented Feb 19, 2026

close #48

実装の概要

実習メニュー画面を更新し、SkillTree関連の変更をロールバックしました。

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

採用したアプローチ

  • 手法:
    • 実習メニュー (ExerciseMenu.tsx) を 3x3 のグリッドレイアウトに変更
    • 項目を Web, Mobile, Network, Game, Design, Infrastructure, AI, Security に更新
    • 画像リソースを追加・更新
    • 不要になった SkillTreeDisplay コンポーネントを削除し、DashboardContainer を元の状態に戻した
  • メリット:
    • ユーザーが見やすいレイアウトになった
    • 必要な項目が明確になった
  • デメリット/リスク:
    • 特になし

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

  • 手法: 特になし
  • 却下理由: -

🧪 テスト戦略と範囲

追加したテストケース

  • 正常系: 実習メニュー画面が正しく表示されること
  • 異常系: 画像読み込みエラー時のフォールバック処理
  • テストしていないこと: モックデータを使用しているため、実際のAPI連携は未実施

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

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

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

実習メニューの画像やラベルが正しいか確認してください。

@kdix-23-071
Copy link
Contributor Author

スキルツリーのロールバックまで入れんでもよろしですCopilot
Screenshot 2026-02-20 at 2 04 55

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は、実習メニュー画面の新規実装と、ダッシュボードのスキルロードマップコンポーネントの復元を行っています。実習メニューは8つのカテゴリ(Web、Mobile、Network、Game、Design、Infrastructure、AI、Security)を3x3グリッドで表示し、サイドバーにアクティブ状態のハイライト機能を追加しています。

Changes:

  • 実習メニューコンポーネント(ExerciseMenu.tsx)を3x3グリッドレイアウトで新規作成
  • タブナビゲーション機能を追加(通常演習、書籍、ターミナル)
  • AppSidebarにアクティブパス検出機能を実装し、現在のページを視覚的にハイライト
  • DashboardContainerにSkillRoadmapコンポーネントを復元(プレースホルダーから置き換え)
  • /exercisesルート用のページとレイアウトを作成

Reviewed changes

Copilot reviewed 5 out of 13 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
frontend/src/features/exercise/components/ExerciseMenu.tsx 演習メニューの新規実装。3x3グリッド、タブナビゲーション、8つの演習カテゴリを含む
frontend/src/features/dashboard/components/DashboardContainer.tsx ツリー絵文字プレースホルダーをSkillRoadmapコンポーネントに置き換え
frontend/src/features/dashboard/components/AppSidebar.tsx アクティブパス検出ロジックを追加し、現在のページをハイライト表示
frontend/src/app/exercises/page.tsx ExerciseMenuコンポーネントをレンダリングする演習ページを作成
frontend/src/app/exercises/layout.tsx ヘッダーとサイドバーを含む演習用レイアウトを作成

// Dummy data for the cards (9 placeholders for 3x3 grid)
const items = Array.from({ length: 9 }).map((_, i) => ({
id: i,
title: i === 0 ? 'Web' : i === 1 ? 'Mobile' : i === 2 ? 'Network' : i === 3 ? 'Game' : i === 4 ? 'Design' : i === 5 ? 'Infrastructure' : i === 6 ? 'AI' : i === 7 ? 'Security' : i === 8 ? 'Comming Soon...':`Exercise ${i + 1}`,
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

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

「Comming Soon...」のスペルミスがあります。「Coming Soon...」に修正してください。

Suggested change
title: i === 0 ? 'Web' : i === 1 ? 'Mobile' : i === 2 ? 'Network' : i === 3 ? 'Game' : i === 4 ? 'Design' : i === 5 ? 'Infrastructure' : i === 6 ? 'AI' : i === 7 ? 'Security' : i === 8 ? 'Comming Soon...':`Exercise ${i + 1}`,
title: i === 0 ? 'Web' : i === 1 ? 'Mobile' : i === 2 ? 'Network' : i === 3 ? 'Game' : i === 4 ? 'Design' : i === 5 ? 'Infrastructure' : i === 6 ? 'AI' : i === 7 ? 'Security' : i === 8 ? 'Coming Soon...':`Exercise ${i + 1}`,

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

fix done

Comment on lines 21 to 25
// Dummy data for the cards (9 placeholders for 3x3 grid)
const items = Array.from({ length: 9 }).map((_, i) => ({
id: i,
title: i === 0 ? 'Web' : i === 1 ? 'Mobile' : i === 2 ? 'Network' : i === 3 ? 'Game' : i === 4 ? 'Design' : i === 5 ? 'Infrastructure' : i === 6 ? 'AI' : i === 7 ? 'Security' : i === 8 ? 'Comming Soon...':`Exercise ${i + 1}`,
image: i === 0 ? '/images/exercises/Web.png' : i === 1 ? '/images/exercises/Mobile.png' : i === 2 ? '/images/exercises/Network.png' : i === 3 ? '/images/exercises/game.png' : i === 4 ? '/images/exercises/Design.png' : i === 5 ? '/images/exercises/Infr.png' : i === 6 ? '/images/exercises/ai.png' : i === 7 ? '/images/exercises/Security.png' : null,
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

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

この行の極めて長いネストされた三項演算子は、保守性とコードの可読性を大きく損ないます。代わりに、配列データとして定義し、マッピングする方が明確で保守しやすくなります。例えば、['Web', 'Mobile', 'Network', 'Game', 'Design', 'Infrastructure', 'AI', 'Security', 'Coming Soon...']という配列を定義し、items配列を生成する際にそのインデックスで参照する方法が推奨されます。

Suggested change
// Dummy data for the cards (9 placeholders for 3x3 grid)
const items = Array.from({ length: 9 }).map((_, i) => ({
id: i,
title: i === 0 ? 'Web' : i === 1 ? 'Mobile' : i === 2 ? 'Network' : i === 3 ? 'Game' : i === 4 ? 'Design' : i === 5 ? 'Infrastructure' : i === 6 ? 'AI' : i === 7 ? 'Security' : i === 8 ? 'Comming Soon...':`Exercise ${i + 1}`,
image: i === 0 ? '/images/exercises/Web.png' : i === 1 ? '/images/exercises/Mobile.png' : i === 2 ? '/images/exercises/Network.png' : i === 3 ? '/images/exercises/game.png' : i === 4 ? '/images/exercises/Design.png' : i === 5 ? '/images/exercises/Infr.png' : i === 6 ? '/images/exercises/ai.png' : i === 7 ? '/images/exercises/Security.png' : null,
const EXERCISE_TITLES = [
'Web',
'Mobile',
'Network',
'Game',
'Design',
'Infrastructure',
'AI',
'Security',
'Comming Soon...',
];
const EXERCISE_IMAGES = [
'/images/exercises/Web.png',
'/images/exercises/Mobile.png',
'/images/exercises/Network.png',
'/images/exercises/game.png',
'/images/exercises/Design.png',
'/images/exercises/Infr.png',
'/images/exercises/ai.png',
'/images/exercises/Security.png',
null,
];
// Dummy data for the cards (9 placeholders for 3x3 grid)
const items = Array.from({ length: 9 }).map((_, i) => ({
id: i,
title: EXERCISE_TITLES[i] ?? `Exercise ${i + 1}`,
image: EXERCISE_IMAGES[i] ?? null,

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

fix

Comment on lines 22 to 25
const items = Array.from({ length: 9 }).map((_, i) => ({
id: i,
title: i === 0 ? 'Web' : i === 1 ? 'Mobile' : i === 2 ? 'Network' : i === 3 ? 'Game' : i === 4 ? 'Design' : i === 5 ? 'Infrastructure' : i === 6 ? 'AI' : i === 7 ? 'Security' : i === 8 ? 'Comming Soon...':`Exercise ${i + 1}`,
image: i === 0 ? '/images/exercises/Web.png' : i === 1 ? '/images/exercises/Mobile.png' : i === 2 ? '/images/exercises/Network.png' : i === 3 ? '/images/exercises/game.png' : i === 4 ? '/images/exercises/Design.png' : i === 5 ? '/images/exercises/Infr.png' : i === 6 ? '/images/exercises/ai.png' : i === 7 ? '/images/exercises/Security.png' : null,
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

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

この行も極めて長いネストされた三項演算子で構成されており、保守性が非常に低くなっています。画像パスを配列として定義し、インデックスでアクセスする方法に変更することを強く推奨します。また、画像ファイル名の命名規則が一貫していません(Web.png、Mobile.png、Design.png、Security.pngは大文字始まり、game.png、ai.pngは小文字始まり、Infr.pngは省略形)。統一した命名規則に従うべきです。

Suggested change
const items = Array.from({ length: 9 }).map((_, i) => ({
id: i,
title: i === 0 ? 'Web' : i === 1 ? 'Mobile' : i === 2 ? 'Network' : i === 3 ? 'Game' : i === 4 ? 'Design' : i === 5 ? 'Infrastructure' : i === 6 ? 'AI' : i === 7 ? 'Security' : i === 8 ? 'Comming Soon...':`Exercise ${i + 1}`,
image: i === 0 ? '/images/exercises/Web.png' : i === 1 ? '/images/exercises/Mobile.png' : i === 2 ? '/images/exercises/Network.png' : i === 3 ? '/images/exercises/game.png' : i === 4 ? '/images/exercises/Design.png' : i === 5 ? '/images/exercises/Infr.png' : i === 6 ? '/images/exercises/ai.png' : i === 7 ? '/images/exercises/Security.png' : null,
const TITLES = [
'Web',
'Mobile',
'Network',
'Game',
'Design',
'Infrastructure',
'AI',
'Security',
'Comming Soon...',
] as const;
const IMAGES = [
'/images/exercises/Web.png',
'/images/exercises/Mobile.png',
'/images/exercises/Network.png',
'/images/exercises/game.png',
'/images/exercises/Design.png',
'/images/exercises/Infr.png',
'/images/exercises/ai.png',
'/images/exercises/Security.png',
null,
] as const;
const items = Array.from({ length: 9 }).map((_, i) => ({
id: i,
title: TITLES[i] ?? `Exercise ${i + 1}`,
image: IMAGES[i] ?? null,

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

fix done

];

export function ExerciseMenu() {
const [activeTab, setActiveTab] = useState<TabType>('regular');
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

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

activeTabステートが定義され、タブのクリックで更新されていますが、実際にコンテンツのフィルタリングや表示制御に使用されていません。このステートを使用してタブごとに異なるコンテンツを表示する実装が必要です。もし現時点でタブの機能が不要であれば、activeTabステートとsetActiveTabの定義を削除し、タブのボタンもクリック不可にするか、視覚的なフィードバックのみにすることを検討してください。

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

ページ増やす時にいるのでignore

Comment on lines +1 to +35
import { AppSidebar } from '@/features/dashboard/components/AppSidebar';

export const metadata = {
title: 'Exercises',
description: 'Your coding exercises',
};

export default function ExerciseLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<div className="min-h-screen bg-[#FDFEF0]">
{/* Top Header Bar - Full Width */}
<header className="sticky top-0 z-50 flex h-16 w-full items-center justify-end bg-[#559C71] px-6 text-white shadow-md">
<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>
</header>

<div className="flex">
<AppSidebar />

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

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

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

exercises/layout.tsxとdashboard/layout.tsxの間でレイアウトコードが重複しています。ヘッダーバー、サイドバー、メインコンテンツエリアの構造がほぼ同一です。共通のレイアウトコンポーネントを作成し、両方のレイアウトから再利用することで、保守性が向上し、将来的な変更が容易になります。例えば、AppLayoutコンポーネントを作成し、それを両方のレイアウトファイルから使用することを検討してください。

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

別issue切り出し

Comment on lines 57 to 59
<div
key={item.id}
className="flex aspect-square w-full flex-col items-center justify-center rounded-3xl border-2 border-[#3A7E56] bg-white p-4 shadow-sm transition-transform hover:scale-105 hover:shadow-md cursor-pointer overflow-hidden"
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

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

カードにcursor-pointerクラスが適用されていますが、クリック可能な要素としてdiv要素を使用しています。これはアクセシビリティの問題を引き起こします。キーボードナビゲーションやスクリーンリーダーのサポートが欠如しています。button要素またはLink要素を使用し、適切なrole、tabIndex、onKeyDownハンドラーを追加することを推奨します。また、クリックハンドラーが実装されていないため、現状では実際にクリックしても何も起こりません。

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

buttonにfix

return "ml-3 text-lg text-[#D16B36] font-bold";
}
return "ml-3 text-lg";
}
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

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

getTextClass関数の定義の最後にセミコロンがありません。コードの一貫性のため、セミコロンを追加するか、プロジェクト全体でセミコロンを省略する規則に従ってください。

Suggested change
}
};

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

fix

Comment on lines +84 to +86
{/* Skill Tree Section */}
<div className="flex justify-center py-8">
{/* Using a placeholder for the tree image based on the mockup description */}
<div className="relative h-64 w-64">
{/* Fallback to a large tree emoji if no image provided */}
<div className="flex h-full w-full items-center justify-center text-[10rem]">
🌳
</div>
</div>
<SkillRoadmap skills={userStatus.skillRoadmap} />
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

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

PR説明では「SkillTree関連の変更をロールバック」と記載されていますが、実際のコード変更では、ツリー絵文字のプレースホルダーを削除してSkillRoadmapコンポーネントを追加しています。これは「ロールバック」ではなく、「SkillRoadmapコンポーネントの復元」または「SkillRoadmapコンポーネントの実装」と表現する方が正確です。説明と実装の不一致にご注意ください。

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

これはごめん

@Inlet-back Inlet-back merged commit 7900399 into develop Feb 20, 2026
4 checks passed
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.

feat: 演習メニュー画面の実装

3 participants