Skip to content

feat: Feature-based Architecture採用によるダッシュボード機能の実装#34

Merged
Neon-straySheep merged 1 commit intodevelopfrom
feature/issue-23-ui-mock
Feb 18, 2026
Merged

feat: Feature-based Architecture採用によるダッシュボード機能の実装#34
Neon-straySheep merged 1 commit intodevelopfrom
feature/issue-23-ui-mock

Conversation

@Neon-straySheep
Copy link
Contributor

実装内容

ディレクトリ構成

  • frontend/src/features/dashboard/ にダッシュボード機能を完全隔離
  • components/ - UIコンポーネント(StatusCard, BadgeList, SkillRoadmap, DashboardContainer)
  • types/ - ドメイン固有の型定義(UserStatus, Rank, Badge, SkillNode)
  • api/ - モックデータAPI

コンポーネント

  • StatusCard.tsx - ユーザーレベル、経験値、進捗表示
  • BadgeList.tsx - 獲得済み・未獲得バッジの表示
  • SkillRoadmap.tsx - 階層的なスキルツリー表示
  • DashboardContainer.tsx - データフェッチと全体管理

設計理由

  • Feature-basedの採用により、ドメインごとの機能隔離が実現
  • app/(dashboard)ページレイヤーではロジックを持たず、features/dashboardに責務を集約
  • future-proofな構成で、今後の機能追加もスケーラブルに対応可能

セキュリティ考慮

  • モックデータの利用(バックエンド実装時に切り替え予定)
  • 型安全性をTypeScriptで確保

タイプセーフティ

  • 全コンポーネントに厳密な型定義を適用
  • ダークモード対応(Tailwind CSS dark: class使用)

実装の概要

技術的な意思決定と「なぜ」

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

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

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

備考

mock含め型定義部分は改修予定

## 実装内容
- Feature-based Architectureに基づく`src/features/dashboard`ディレクトリ構造を構築
- ダッシュボード機能専用の以下のコンポーネントを実装:
  - StatusCard.tsx: ユーザーレベル、経験値、進捗表示
  - BadgeList.tsx: 獲得済み・未獲得バッジの表示
  - SkillRoadmap.tsx: 階層的なスキルツリーの表示
  - DashboardContainer.tsx: データフェッチと全体管理

## 設計理由
- Feature-basedの採用により、ドメインごとの機能隔離が実現
- `app/(dashboard)`ページレイヤーではロジックを持たず、`features/dashboard`に責務を集約
- future-proofな構成で、今後の機能追加もスケーラブルに対応可能

## セキュリティ考慮
- モックデータの利用(バックエンド実装時に切り替え予定)
- 型安全性をTypeScriptで確保

## タイプセーフティ
- 全コンポーネントに厳密な型定義を適用
- ダークモード対応(Tailwind CSS `dark:` class使用)
Copy link

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

ダッシュボード機能を frontend/src/features/dashboard/ 配下に隔離し、App Router の (dashboard) ルートから表示できるようにするPRです(Feature-based Architecture の導入例としての実装)。

Changes:

  • features/dashboard に型定義・モックAPI・UIコンポーネント・統合コンテナを新規追加
  • app/(dashboard) にページ/レイアウトを追加してダッシュボードを表示
  • ルート app/layout.tsx のメタデータと言語設定を更新

Reviewed changes

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

Show a summary per file
File Description
frontend/src/features/dashboard/types/index.ts ダッシュボードドメイン型を定義
frontend/src/features/dashboard/api/mock.ts ダッシュボード用モックデータ取得関数を追加
frontend/src/features/dashboard/components/StatusCard.tsx ユーザーステータス(ランク/EXP等)の表示を追加
frontend/src/features/dashboard/components/BadgeList.tsx 獲得/未獲得バッジ表示を追加
frontend/src/features/dashboard/components/SkillRoadmap.tsx スキルツリーの再帰表示を追加
frontend/src/features/dashboard/components/DashboardContainer.tsx データフェッチ/状態管理/各コンポーネント統合を追加
frontend/src/app/layout.tsx アプリ全体のmetadataと言語属性を変更
frontend/src/app/(dashboard)/page.tsx ダッシュボードページを追加
frontend/src/app/(dashboard)/layout.tsx ダッシュボード用レイアウト/metadata を追加

Comment on lines +88 to +100
{/* 子スキル */}
{childSkills.length > 0 && (
<div className="mt-4 space-y-4">
{childSkills.map((childSkill) => (
<SkillNode
key={childSkill.id}
skill={childSkill}
allSkills={allSkills}
depth={depth + 1}
/>
))}
</div>
)}
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

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

子要素の描画がデータに完全依存した再帰になっているため、prerequisites に循環参照が含まれると無限再帰(スタックオーバーフロー)になります。visited セットでの循環検知や最大深度制限などのガードを入れてください。

Copilot uses AI. Check for mistakes.
Comment on lines +25 to +40
const loadDashboard = async () => {
try {
setLoading(true);
const data = await fetchUserDashboard(userId);
setUserStatus(data);
setError(null);
} catch (err) {
setError(
err instanceof Error ? err.message : 'ダッシュボードの読み込みに失敗しました'
);
} finally {
setLoading(false);
}
};

loadDashboard();
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

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

useEffect 内の非同期処理がコンポーネントのアンマウント後も setState し得るため、Reactの警告や不要な更新の原因になります。クリーンアップでキャンセルフラグ(または AbortController)を扱い、アンマウント後は state 更新しないようにしてください。

Suggested change
const loadDashboard = async () => {
try {
setLoading(true);
const data = await fetchUserDashboard(userId);
setUserStatus(data);
setError(null);
} catch (err) {
setError(
err instanceof Error ? err.message : 'ダッシュボードの読み込みに失敗しました'
);
} finally {
setLoading(false);
}
};
loadDashboard();
let isCancelled = false;
const loadDashboard = async () => {
try {
setLoading(true);
const data = await fetchUserDashboard(userId);
if (isCancelled) {
return;
}
setUserStatus(data);
setError(null);
} catch (err) {
if (isCancelled) {
return;
}
setError(
err instanceof Error ? err.message : 'ダッシュボードの読み込みに失敗しました'
);
} finally {
if (!isCancelled) {
setLoading(false);
}
}
};
loadDashboard();
return () => {
isCancelled = true;
};

Copilot uses AI. Check for mistakes.
Comment on lines +19 to +25
export function DashboardContainer({ userId = 'default-user' }: DashboardContainerProps) {
const [userStatus, setUserStatus] = useState<UserStatus | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);

useEffect(() => {
const loadDashboard = async () => {
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

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

userId のデフォルト値が固定文字列(default-user)だと、呼び出し側が渡し忘れた場合に意図せず別ユーザーのダッシュボードとして扱われます。必須にするか、未指定時はエラー/未ログイン表示に寄せる方が安全です。

Suggested change
export function DashboardContainer({ userId = 'default-user' }: DashboardContainerProps) {
const [userStatus, setUserStatus] = useState<UserStatus | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const loadDashboard = async () => {
export function DashboardContainer({ userId }: DashboardContainerProps) {
const [userStatus, setUserStatus] = useState<UserStatus | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const loadDashboard = async () => {
// userId が未指定の場合は安全側に倒してエラー扱いとし、API 呼び出しを行わない
if (!userId) {
setError('ユーザーIDが指定されていません');
setUserStatus(null);
setLoading(false);
return;
}

Copilot uses AI. Check for mistakes.
Comment on lines 15 to 18
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
title: "Dashboard | Skill Tree",
description: "Your personal skill development dashboard",
};
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

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

PR説明はダッシュボード機能追加が主旨ですが、アプリ全体の metadata(title/description)をダッシュボード前提の文言に変更しています。ホーム等の他ページも含めた全体影響があるので、ダッシュボード配下に限定する(app/(dashboard)/layout.tsx 側へ寄せる)か、PR説明に意図を明記してください。

Copilot uses AI. Check for mistakes.
Comment on lines +6 to +41
export interface Badge {
id: string;
name: string;
icon: string;
description: string;
unlockedAt?: Date;
}

export interface Rank {
level: number;
title: string;
progress: number; // 0-100
nextLevelExp?: number;
}

export interface SkillNode {
id: string;
name: string;
description: string;
icon: string;
completed: boolean;
level: number;
prerequisites?: string[];
}

export interface UserStatus {
userId: string;
displayName: string;
avatar?: string;
totalExp: number;
currentRank: Rank;
badges: Badge[];
skillRoadmap: SkillNode[];
joinedAt: Date;
lastActivityAt: Date;
}
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

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

unlockedAt / joinedAt / lastActivityAtDate 型にしているため、将来バックエンドAPI(JSON)から取得する際に型と実データ(ISO文字列)にズレが出ます。API境界の型として使うなら string(ISO 8601)に寄せるか、string | Date としてデシリアライズ層(api内)で Date へ変換する形にしておくと移行が楽です。

Copilot uses AI. Check for mistakes.
Comment on lines +26 to 27
<html lang="ja">
<body
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

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

<html lang="ja"> への変更はサイト全体の言語宣言に影響します(既存のホームは英語文言)。i18n方針が未確定なら、まずはダッシュボード配下のみに限定するか、全ページ文言/翻訳対応とセットで変更するのが安全です。

Copilot uses AI. Check for mistakes.
Comment on lines +6 to +9
import { DashboardContainer } from '@/features/dashboard/components/DashboardContainer';

export default function DashboardPage() {
return <DashboardContainer userId="user-123" />;
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

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

ページ側で userId="user-123" を固定しており、PR説明の「ページレイヤーではロジックを持たない/責務を features に集約」と矛盾します。少なくとも固定値は避け、未指定(デフォルト)にするか、認証/ルートパラメータから取得する形に寄せてください。

Copilot uses AI. Check for mistakes.
@@ -0,0 +1,17 @@
/**
* Dashboard Layout
* ダッシュボード機能用のレイアウト(ロジックはなく、フロートのみ)
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

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

コメントの「フロートのみ」は表現として意味が取りづらく、意図(例: 「表示のみ」「レイアウトのみ」「ラッパーのみ」等)が伝わりません。内容に合う語に修正して、読む人が誤解しないようにしてください。

Suggested change
* ダッシュボード機能用のレイアウト(ロジックはなく、フロートのみ
* ダッシュボード機能用のレイアウト(ロジックはなく、レイアウト定義のみ

Copilot uses AI. Check for mistakes.
Comment on lines +5 to +6

export const metadata = {
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

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

metadata が型付けされていないため、キー名のタイポ等がコンパイル時に検出されません。app/layout.tsx では Metadata 型を付与しているので、ここも同様に Metadata を付けて整合性を取ると安全です。

Suggested change
export const metadata = {
import type { Metadata } from 'next';
export const metadata: Metadata = {

Copilot uses AI. Check for mistakes.
Comment on lines +39 to +42
</p>
<p className="mt-1 text-xs text-gray-600 dark:text-gray-400">
{new Date(badge.unlockedAt!).toLocaleDateString('ja-JP')}
</p>
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

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

badge.unlockedAt! の non-null アサーションは、後続のリファクタや型変更時に破綻しやすいです。filter 側を型ガードにして unlockedAt が必ず存在する配列型に絞り込む形にすると安全に扱えます。

Copilot uses AI. Check for mistakes.
@Neon-straySheep Neon-straySheep merged commit 75eb350 into develop Feb 18, 2026
10 checks passed
@Neon-straySheep Neon-straySheep deleted the feature/issue-23-ui-mock branch February 18, 2026 14:00
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.

1 participant

Comments