From 9011f93dfefc3db181ac9e0cdaef842319eedc44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=84=AD=E6=B2=9B=E8=BB=92?= Date: Wed, 1 Oct 2025 16:15:17 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=8C=E6=88=90=E5=89=8D=E7=AB=AF?= =?UTF-8?q?=E5=A4=A7=E8=A6=8F=E6=A8=A1=E6=9E=B6=E6=A7=8B=E9=87=8D=E7=B5=84?= =?UTF-8?q?=E8=88=87=E8=A1=93=E8=AA=9E=E7=B5=B1=E4=B8=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 主要完成項目 ### 🏗️ Hooks架構重組 - 刪除62.5%死代碼hooks (5個檔案) - 重組為功能性資料夾結構 (flashcards/, review/) - 修復所有import路徑和類型錯誤 ### 🧹 Lib資料夾優化 - 移除未使用檔案:cn.ts, performance/, errors/, studySession.ts - 統一API配置管理,建立中央化配置 - 清理硬編碼URL,提升可維護性 ### 📝 術語統一 Study→Review - API端點:/study/* → /review/* - 客戶端:studyApiClient → reviewApiClient - 配置項:STUDY → REVIEW - 註釋更新:StudyRecord → ReviewRecord ### ✅ 技術成果 - 前端編譯100%成功,無錯誤 - 減少檔案數量31% (lib資料夾) - 消除重複代碼和架構冗餘 - 建立企業級前端架構標準 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- frontend/app/dashboard/page.tsx | 4 +- frontend/app/flashcards/[id]/page.tsx | 6 +- frontend/app/flashcards/page.tsx | 10 +- frontend/app/generate/page.tsx | 8 +- frontend/app/review-design/page.tsx | 4 +- frontend/app/review/page.tsx | 4 +- frontend/components/debug/TestDebugPanel.tsx | 20 +- .../components/flashcards/FlashcardForm.tsx | 12 +- .../review-tests/SentenceSpeakingTest.tsx | 22 +- .../review/shared/BaseTestComponent.tsx | 2 +- frontend/hooks/flashcards/useDebounce.ts | 21 ++ .../{ => flashcards}/useFlashcardSearch.ts | 0 frontend/hooks/review/useProgressTracker.ts | 66 ----- frontend/hooks/review/useTestAnswering.ts | 159 ----------- frontend/hooks/review/useTestQueue.ts | 4 +- frontend/hooks/useAudio.ts | 228 ---------------- frontend/hooks/useDebounce.ts | 82 ------ frontend/hooks/useReviewLogic.ts | 92 ------- frontend/lib/api/client.ts | 18 +- frontend/lib/config/api.ts | 42 +++ frontend/lib/errors/errorHandler.ts | 249 ------------------ frontend/lib/performance/index.ts | 209 --------------- frontend/lib/services/auth.ts | 4 +- frontend/lib/services/flashcards.ts | 6 +- frontend/lib/services/imageGeneration.ts | 3 +- frontend/lib/services/review/reviewService.ts | 2 +- frontend/lib/services/studySession.ts | 167 ------------ frontend/lib/utils/cefrUtils.ts | 22 ++ frontend/lib/utils/cn.ts | 6 - frontend/store/useReviewDataStore.ts | 2 +- frontend/types/review.ts | 2 +- 31 files changed, 156 insertions(+), 1320 deletions(-) create mode 100644 frontend/hooks/flashcards/useDebounce.ts rename frontend/hooks/{ => flashcards}/useFlashcardSearch.ts (100%) delete mode 100644 frontend/hooks/review/useProgressTracker.ts delete mode 100644 frontend/hooks/review/useTestAnswering.ts delete mode 100644 frontend/hooks/useAudio.ts delete mode 100644 frontend/hooks/useDebounce.ts delete mode 100644 frontend/hooks/useReviewLogic.ts create mode 100644 frontend/lib/config/api.ts delete mode 100644 frontend/lib/errors/errorHandler.ts delete mode 100644 frontend/lib/performance/index.ts delete mode 100644 frontend/lib/services/studySession.ts delete mode 100644 frontend/lib/utils/cn.ts diff --git a/frontend/app/dashboard/page.tsx b/frontend/app/dashboard/page.tsx index 9423dc7..468db0d 100644 --- a/frontend/app/dashboard/page.tsx +++ b/frontend/app/dashboard/page.tsx @@ -2,8 +2,8 @@ import Link from 'next/link' import { useState } from 'react' -import { ProtectedRoute } from '@/components/ProtectedRoute' -import { Navigation } from '@/components/Navigation' +import { ProtectedRoute } from '@/components/shared/ProtectedRoute' +import { Navigation } from '@/components/shared/Navigation' import { useAuth } from '@/contexts/AuthContext' function DashboardContent() { diff --git a/frontend/app/flashcards/[id]/page.tsx b/frontend/app/flashcards/[id]/page.tsx index 19e5df9..67f33c0 100644 --- a/frontend/app/flashcards/[id]/page.tsx +++ b/frontend/app/flashcards/[id]/page.tsx @@ -2,9 +2,9 @@ import { useState, useEffect, use } from 'react' import { useRouter, useSearchParams } from 'next/navigation' -import { Navigation } from '@/components/Navigation' -import { ProtectedRoute } from '@/components/ProtectedRoute' -import { useToast } from '@/components/Toast' +import { Navigation } from '@/components/shared/Navigation' +import { ProtectedRoute } from '@/components/shared/ProtectedRoute' +import { useToast } from '@/components/shared/Toast' import { flashcardsService, type Flashcard } from '@/lib/services/flashcards' import { imageGenerationService } from '@/lib/services/imageGeneration' diff --git a/frontend/app/flashcards/page.tsx b/frontend/app/flashcards/page.tsx index 165b88f..f3c02be 100644 --- a/frontend/app/flashcards/page.tsx +++ b/frontend/app/flashcards/page.tsx @@ -3,13 +3,13 @@ import { useState, useEffect } from 'react' import Link from 'next/link' import { useRouter } from 'next/navigation' -import { ProtectedRoute } from '@/components/ProtectedRoute' -import { Navigation } from '@/components/Navigation' -import { FlashcardForm } from '@/components/FlashcardForm' -import { useToast } from '@/components/Toast' +import { ProtectedRoute } from '@/components/shared/ProtectedRoute' +import { Navigation } from '@/components/shared/Navigation' +import { FlashcardForm } from '@/components/flashcards/FlashcardForm' +import { useToast } from '@/components/shared/Toast' import { flashcardsService, type Flashcard } from '@/lib/services/flashcards' import { imageGenerationService } from '@/lib/services/imageGeneration' -import { useFlashcardSearch, type SearchActions } from '@/hooks/useFlashcardSearch' +import { useFlashcardSearch, type SearchActions } from '@/hooks/flashcards/useFlashcardSearch' // 詞性簡寫轉換 (全域函數) const getPartOfSpeechDisplay = (partOfSpeech: string): string => { diff --git a/frontend/app/generate/page.tsx b/frontend/app/generate/page.tsx index d7e4c65..fc384fb 100644 --- a/frontend/app/generate/page.tsx +++ b/frontend/app/generate/page.tsx @@ -1,10 +1,10 @@ 'use client' import { useState, useMemo, useCallback } from 'react' -import { ProtectedRoute } from '@/components/ProtectedRoute' -import { Navigation } from '@/components/Navigation' -import { ClickableTextV2 } from '@/components/ClickableTextV2' -import { useToast } from '@/components/Toast' +import { ProtectedRoute } from '@/components/shared/ProtectedRoute' +import { Navigation } from '@/components/shared/Navigation' +import { ClickableTextV2 } from '@/components/generate/ClickableTextV2' +import { useToast } from '@/components/shared/Toast' import { flashcardsService } from '@/lib/services/flashcards' import { compareCEFRLevels, getLevelIndex, getTargetLearningRange } from '@/lib/utils/cefrUtils' import { Play } from 'lucide-react' diff --git a/frontend/app/review-design/page.tsx b/frontend/app/review-design/page.tsx index 44b08bd..596b9ac 100644 --- a/frontend/app/review-design/page.tsx +++ b/frontend/app/review-design/page.tsx @@ -1,7 +1,7 @@ 'use client' import { useState, useEffect } from 'react' -import { Navigation } from '@/components/Navigation' +import { Navigation } from '@/components/shared/Navigation' import { FlipMemoryTest, VocabChoiceTest, @@ -50,6 +50,7 @@ export default function ReviewTestsPage() { pronunciation: currentCard.pronunciation, synonyms: currentCard.synonyms || [], difficultyLevel: currentCard.difficultyLevel, + cefr: currentCard.difficultyLevel || 'A1', translation: currentCard.translation, // 從 flashcardExampleImages 提取圖片URL exampleImage: currentCard.flashcardExampleImages?.[0]?.exampleImage ? @@ -64,6 +65,7 @@ export default function ReviewTestsPage() { pronunciation: "", synonyms: [], difficultyLevel: "A1", + cefr: "A1", translation: "載入中", exampleImage: undefined } diff --git a/frontend/app/review/page.tsx b/frontend/app/review/page.tsx index 4ca7683..3b2f65d 100644 --- a/frontend/app/review/page.tsx +++ b/frontend/app/review/page.tsx @@ -2,8 +2,8 @@ import { useEffect } from 'react' import { useRouter } from 'next/navigation' -import { Navigation } from '@/components/Navigation' -import LearningComplete from '@/components/LearningComplete' +import { Navigation } from '@/components/shared/Navigation' +import LearningComplete from '@/components/flashcards/LearningComplete' import { Modal } from '@/components/ui/Modal' // 新架構組件 diff --git a/frontend/components/debug/TestDebugPanel.tsx b/frontend/components/debug/TestDebugPanel.tsx index cda190c..2e940f4 100644 --- a/frontend/components/debug/TestDebugPanel.tsx +++ b/frontend/components/debug/TestDebugPanel.tsx @@ -9,20 +9,14 @@ interface TestDebugPanelProps { export const TestDebugPanel: React.FC = ({ className }) => { const [isVisible, setIsVisible] = useState(false) - const { testItems, currentTestIndex, addTestItems, resetQueue } = useTestQueueStore() - const { totalCorrect, totalIncorrect, resetScore } = useTestResultStore() + const { testItems, currentTestIndex, initializeTestQueue, resetQueue } = useTestQueueStore() + const { score, resetScore } = useTestResultStore() const stats = getTestStatistics(mockFlashcards) const handleLoadMockData = () => { - const queue = generateTestQueue(mockFlashcards) - addTestItems(queue.map(item => ({ - flashcardId: item.card.id, - mode: item.mode, - priority: item.priority, - attempts: item.card.testAttempts || 0, - completed: false - }))) + // 使用 initializeTestQueue 期望的參數格式 + initializeTestQueue(mockFlashcards, []) } const handleResetAll = () => { @@ -59,7 +53,7 @@ export const TestDebugPanel: React.FC = ({ className }) =>
隊列長度: {testItems.length}
當前位置: {currentTestIndex + 1}/{testItems.length}
-
正確: {totalCorrect} | 錯誤: {totalIncorrect}
+
正確: {score.correct} | 錯誤: {score.total - score.correct}
@@ -104,8 +98,8 @@ export const TestDebugPanel: React.FC = ({ className }) => key={index} className={`flex justify-between ${index === currentTestIndex ? 'font-bold text-blue-600' : ''}`} > - {item.mode} - P:{item.priority} + {item.testName} + #{item.order} ))} {testItems.length > 10 && ( diff --git a/frontend/components/flashcards/FlashcardForm.tsx b/frontend/components/flashcards/FlashcardForm.tsx index 038adab..242e5e5 100644 --- a/frontend/components/flashcards/FlashcardForm.tsx +++ b/frontend/components/flashcards/FlashcardForm.tsx @@ -2,7 +2,7 @@ import React, { useState } from 'react' import { flashcardsService, type CreateFlashcardRequest, type Flashcard } from '@/lib/services/flashcards' -import AudioPlayer from './AudioPlayer' +import AudioPlayer from '@/components/media/AudioPlayer' interface FlashcardFormProps { cardSets?: any[] // 保持相容性 @@ -21,7 +21,7 @@ export function FlashcardForm({ initialData, isEdit = false, onSuccess, onCancel partOfSpeech: initialData?.partOfSpeech || 'noun', example: initialData?.example || '', exampleTranslation: initialData?.exampleTranslation || '', - difficultyLevel: initialData?.difficultyLevel || 'A2', + cefr: initialData?.cefr || 'A2', }) const [loading, setLoading] = useState(false) @@ -156,13 +156,13 @@ export function FlashcardForm({ initialData, isEdit = false, onSuccess, onCancel
-