Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added frontend/public/images/exercises/Design.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added frontend/public/images/exercises/Game.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added frontend/public/images/exercises/Mobile.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added frontend/public/images/exercises/Network.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added frontend/public/images/exercises/Security.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added frontend/public/images/exercises/Web.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added frontend/public/images/exercises/ai.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
35 changes: 35 additions & 0 deletions frontend/src/app/exercises/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,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>
);
}
Comment on lines +1 to +35
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切り出し

16 changes: 16 additions & 0 deletions frontend/src/app/exercises/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
'use client';

/**
* Exercise Menu Page
*/

import React from 'react';
import { ExerciseMenu } from '@/features/exercise/components/ExerciseMenu';

export default function ExercisePage() {
return (
<div className="h-full w-full">
<ExerciseMenu />
</div>
);
}
35 changes: 28 additions & 7 deletions frontend/src/features/dashboard/components/AppSidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,43 +1,64 @@
'use client';

import Link from 'next/link';
import { usePathname } from 'next/navigation';

export function AppSidebar() {
const pathname = usePathname();

const isActive = (path: string) => {
return pathname === path || pathname?.startsWith(`${path}/`);
};

const getLinkClass = (path: string) => {
if (isActive(path)) {
return "flex items-center rounded-r-full bg-[#B8CAA8] p-3 text-gray-900 group";
}
return "flex items-center rounded-lg p-3 text-white hover:bg-[#468B62] group";
};

const getTextClass = (path: string) => {
if (isActive(path)) {
return "ml-3 text-lg text-[#D16B36] font-bold";
}
return "ml-3 text-lg";
};

return (
<aside className="fixed left-0 top-16 z-40 h-[calc(100vh-4rem)] w-64 -translate-x-full transition-transform sm:translate-x-0 bg-[#559C71] text-white">
<div className="flex h-full flex-col overflow-y-auto px-3 py-4">
{/* Navigation Items */}
<ul className="space-y-4 font-medium">
{/* Home - Active state */}
{/* Home */}
<li>
<Link
href="/dashboard"
className="flex items-center rounded-r-full bg-[#B8CAA8] p-3 text-gray-900 group"
className={getLinkClass('/dashboard')}
>
<div className="flex h-10 w-10 items-center justify-center text-2xl">🏠</div>
<span className="ml-3 text-lg text-[#D16B36]">ホーム</span>
<span className={getTextClass('/dashboard')}>ホーム</span>
</Link>
</li>

{/* Exercises */}
<li>
<Link
href="/exercises"
className="flex items-center rounded-lg p-3 text-white hover:bg-[#468B62] group"
className={getLinkClass('/exercises')}
>
<div className="flex h-10 w-10 items-center justify-center text-2xl">📝</div>
<span className="ml-3 text-lg">演習</span>
<span className={getTextClass('/exercises')}>演習</span>
</Link>
</li>

{/* Grades */}
<li>
<Link
href="/grades"
className="flex items-center rounded-lg p-3 text-white hover:bg-[#468B62] group"
className={getLinkClass('/grades')}
>
<div className="flex h-10 w-10 items-center justify-center text-2xl">🔖</div>
<span className="ml-3 text-lg">成績</span>
<span className={getTextClass('/grades')}>成績</span>
</Link>
</li>
</ul>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { useEffect, useState } from 'react';
import type { UserStatus } from '../types';
import { fetchUserDashboard } from '../api/mock';
import { AcquiredBadges } from './AcquiredBadges';
import { SkillRoadmap } from './SkillRoadmap';

interface DashboardContainerProps {
userId?: string;
Expand Down Expand Up @@ -80,15 +81,9 @@ export function DashboardContainer({ userId = 'default-user' }: DashboardContain
</div>
</div>

{/* Tree Image Section */}
{/* 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} />
Comment on lines +84 to +86
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.

これはごめん

</div>

{/* Acquired Badges Section */}
Expand Down
102 changes: 102 additions & 0 deletions frontend/src/features/exercise/components/ExerciseMenu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
'use client';

/**
* ExerciseMenu Component
* 演習メニュー画面(タブとカードグリッド)を表示する
*/

import React, { useState } from 'react';

type TabType = 'regular' | 'book' | 'terminal';

const TAB_ITEMS = [
{ id: 'regular' as TabType, label: '通常演習', icon: '🎓' },
{ id: 'book' as TabType, label: '', icon: '📖' },
{ id: 'terminal' as TabType, label: '', icon: '💻' }, // terminal icon approximation
];

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


const exercises = [
{ title: 'Web', image: '/images/exercises/Web.png' },
{ title: 'Mobile', image: '/images/exercises/Mobile.png' },
{ title: 'Network', image: '/images/exercises/Network.png' },
{ title: 'Game', image: '/images/exercises/Game.png' },
{ title: 'Design', image: '/images/exercises/Design.png' },
{ title: 'Infrastructure', image: '/images/exercises/Infrastructure.png' },
{ title: 'AI', image: '/images/exercises/ai.png' },
{ title: 'Security', image: '/images/exercises/Security.png' },
{ title: 'Coming Soon...', image: null },
];

const items = exercises.map((exercise, i) => ({
id: i,
title: exercise.title,
image: exercise.image,
}));

return (
<div className="flex h-full flex-col">
{/* Tab Navigation Area */}
<div className="flex items-end bg-[#559C71] px-4 pt-4">
{TAB_ITEMS.map((tab) => {
const isActive = activeTab === tab.id;
return (
<button
key={tab.id}
onClick={() => setActiveTab(tab.id)}
className={`mr-1 flex items-center rounded-t-lg px-6 py-2 transition-colors ${
isActive
? 'bg-[#FDFEF0] text-[#559C71]' // Active styling (matches bg)
: 'bg-[#6AB085] text-white hover:bg-[#7BC196]' // Inactive styling
}`}
>
<span className="text-xl">{tab.icon}</span>
{tab.label && <span className="ml-2 font-bold">{tab.label}</span>}
</button>
);
})}
{/* Spacer to fill the rest of the bar if needed */}
<div className="flex-1 border-b border-[#559C71]"></div>
</div>

{/* Main Content Area */}
<div className="flex-1 bg-[#FDFEF0] p-8">
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-3">
{items.map((item) => (
<button
key={item.id}
onClick={() => console.log(`Clicked ${item.title}`)}
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"
>
<div className="relative -mt-4 flex h-4/5 w-full items-center justify-center">
{/* Placeholder for the badge image */}
{item.image ? (
// eslint-disable-next-line @next/next/no-img-element
<img
src={item.image}
alt={item.title}
className="h-full w-full object-contain scale-[1.8]"
onError={(e) => {
e.currentTarget.style.display = 'none';
e.currentTarget.parentElement?.querySelector('.fallback-icon')?.classList.remove('hidden');
}}
/>
) : null}

{/* Fallback Icon (initially hidden if item.image exists) */}
<div className={`fallback-icon h-24 w-24 rounded-full bg-gray-100 flex items-center justify-center text-4xl ${item.image ? 'hidden' : ''}`}>
🏆
</div>
</div>
<h3 className="text-2xl font-bold text-[#1a4023] text-center w-full break-words">
{item.title}
</h3>
</button>
))}
</div>
</div>
</div>
);
}
Loading