feat: 建立開發/正式版本分離的進度組件架構
## 組件重組 - 創建 QuizProgressDev.tsx:給 /review-dev 使用,保留完整開發功能 - 修改 QuizProgress.tsx:給 /review 使用,移除開發測試資訊 - 頁面獨立:兩個頁面使用不同組件,互不影響 ## TypeScript 修復 - 完善 CardState interface 類型兼容性 - 修復 primaryImageUrl, updatedAt, exampleTranslation 類型匹配 - 確保所有必需屬性都有預設值 ## 頁面功能 - /review: 使用簡化版 QuizProgress,適合正式使用 - /review-dev: 使用完整版 QuizProgressDev,保留所有調試功能 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
473ecf4508
commit
d0b6e9e757
|
|
@ -3,7 +3,7 @@
|
||||||
import { Navigation } from '@/components/shared/Navigation'
|
import { Navigation } from '@/components/shared/Navigation'
|
||||||
import { FlipMemory } from '@/components/review/quiz/FlipMemory'
|
import { FlipMemory } from '@/components/review/quiz/FlipMemory'
|
||||||
import { VocabChoiceQuiz } from '@/components/review/quiz/VocabChoiceQuiz'
|
import { VocabChoiceQuiz } from '@/components/review/quiz/VocabChoiceQuiz'
|
||||||
import { QuizProgress } from '@/components/review/ui/QuizProgress'
|
import { QuizProgressDev } from '@/components/review/ui/QuizProgressDev'
|
||||||
import { QuizResult } from '@/components/review/quiz/QuizResult'
|
import { QuizResult } from '@/components/review/quiz/QuizResult'
|
||||||
import { useReviewSession } from '@/hooks/review/useReviewSession'
|
import { useReviewSession } from '@/hooks/review/useReviewSession'
|
||||||
|
|
||||||
|
|
@ -115,7 +115,7 @@ export default function ReviewDevPage() {
|
||||||
<div className="py-8">
|
<div className="py-8">
|
||||||
<div className="max-w-4xl mx-auto px-4">
|
<div className="max-w-4xl mx-auto px-4">
|
||||||
{/* 使用修改後的 SimpleProgress 組件 */}
|
{/* 使用修改後的 SimpleProgress 組件 */}
|
||||||
<QuizProgress
|
<QuizProgressDev
|
||||||
currentQuizItem={currentQuizItem}
|
currentQuizItem={currentQuizItem}
|
||||||
totalQuizItems={totalQuizItems}
|
totalQuizItems={totalQuizItems}
|
||||||
completedQuizItems={completedQuizItems}
|
completedQuizItems={completedQuizItems}
|
||||||
|
|
|
||||||
|
|
@ -114,13 +114,12 @@ export default function ReviewPage() {
|
||||||
|
|
||||||
<div className="py-8">
|
<div className="py-8">
|
||||||
<div className="max-w-4xl mx-auto px-4">
|
<div className="max-w-4xl mx-auto px-4">
|
||||||
{/* 使用修改後的 SimpleProgress 組件 */}
|
{/* 使用簡化的 ReviewProgress 組件 */}
|
||||||
<QuizProgress
|
<QuizProgress
|
||||||
currentQuizItem={currentQuizItem}
|
currentQuizItem={currentQuizItem}
|
||||||
totalQuizItems={totalQuizItems}
|
totalQuizItems={totalQuizItems}
|
||||||
completedQuizItems={completedQuizItems}
|
completedQuizItems={completedQuizItems}
|
||||||
score={score}
|
score={score}
|
||||||
quizItems={quizItems}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* 根據當前測驗項目類型渲染對應組件 */}
|
{/* 根據當前測驗項目類型渲染對應組件 */}
|
||||||
|
|
|
||||||
|
|
@ -22,23 +22,7 @@ export function QuizProgress({ currentQuizItem, totalQuizItems, completedQuizIte
|
||||||
return (
|
return (
|
||||||
<div className="mb-8">
|
<div className="mb-8">
|
||||||
<div className="flex justify-between items-center mb-3">
|
<div className="flex justify-between items-center mb-3">
|
||||||
<div>
|
|
||||||
<span className="text-sm font-medium text-gray-600">線性複習進度</span>
|
|
||||||
{currentQuizItem && (
|
|
||||||
<div className="flex items-center mt-1">
|
|
||||||
<span className="text-lg mr-2">
|
|
||||||
{currentQuizItem.quizType === 'flip-card' ? '🔄' : '🎯'}
|
|
||||||
</span>
|
|
||||||
<span className="text-sm text-gray-500">
|
|
||||||
{currentQuizItem.quizType === 'flip-card' ? '翻卡記憶' : '詞彙選擇'} • {currentQuizItem.cardData.word}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-4 text-sm text-right">
|
<div className="flex items-center gap-4 text-sm text-right">
|
||||||
<span className="text-gray-600">
|
|
||||||
{completedQuizItems}/{totalQuizItems} 項目
|
|
||||||
</span>
|
|
||||||
{score.total > 0 && (
|
{score.total > 0 && (
|
||||||
<span className="text-green-600 font-medium">
|
<span className="text-green-600 font-medium">
|
||||||
準確率 {accuracy}%
|
準確率 {accuracy}%
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
import { QuizItem } from '@/lib/data/reviewSimpleData'
|
||||||
|
|
||||||
|
interface ReviewProgressProps {
|
||||||
|
currentQuizItem?: QuizItem
|
||||||
|
totalQuizItems: number
|
||||||
|
completedQuizItems: number
|
||||||
|
score: { correct: number; total: number }
|
||||||
|
}
|
||||||
|
|
||||||
|
export function QuizProgressDev({ currentQuizItem, totalQuizItems, completedQuizItems, score }: ReviewProgressProps) {
|
||||||
|
const progress = (completedQuizItems / totalQuizItems) * 100
|
||||||
|
const accuracy = score.total > 0 ? Math.round((score.correct / score.total) * 100) : 0
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="mb-8">
|
||||||
|
<div className="flex justify-between items-center mb-3">
|
||||||
|
<div>
|
||||||
|
<span className="text-sm font-medium text-gray-600">複習進度</span>
|
||||||
|
{currentQuizItem && (
|
||||||
|
<div className="flex items-center mt-1">
|
||||||
|
<span className="text-lg mr-2">
|
||||||
|
{currentQuizItem.quizType === 'flip-card' ? '🔄' : '🎯'}
|
||||||
|
</span>
|
||||||
|
<span className="text-sm text-gray-500">
|
||||||
|
{currentQuizItem.quizType === 'flip-card' ? '翻卡記憶' : '詞彙選擇'} • {currentQuizItem.cardData.word}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-4 text-sm text-right">
|
||||||
|
<span className="text-gray-600">
|
||||||
|
{completedQuizItems}/{totalQuizItems} 項目
|
||||||
|
</span>
|
||||||
|
{score.total > 0 && (
|
||||||
|
<span className="text-green-600 font-medium">
|
||||||
|
準確率 {accuracy}%
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 進度條 */}
|
||||||
|
<div className="w-full bg-gray-200 rounded-full h-3">
|
||||||
|
<div
|
||||||
|
className="bg-blue-500 h-3 rounded-full transition-all duration-300"
|
||||||
|
style={{ width: `${progress}%` }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 基本統計 */}
|
||||||
|
<div className="flex justify-center gap-4 mt-3 text-sm">
|
||||||
|
{score.total > 0 && (
|
||||||
|
<>
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<span className="w-2 h-2 bg-green-500 rounded-full"></span>
|
||||||
|
<span className="text-green-700">答對 {score.correct}</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<span className="w-2 h-2 bg-red-500 rounded-full"></span>
|
||||||
|
<span className="text-red-700">答錯 {score.total - score.correct}</span>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -1,14 +1,17 @@
|
||||||
import { useReducer, useEffect, useMemo, useState } from 'react'
|
import { useReducer, useEffect, useMemo, useState } from 'react'
|
||||||
import { flashcardsService, Flashcard } from '@/lib/services/flashcards'
|
import { flashcardsService, Flashcard } from '@/lib/services/flashcards'
|
||||||
|
|
||||||
// 重新定義所需的介面
|
// 重新定義所需的介面,確保與 reviewSimpleData.CardState 完全兼容
|
||||||
interface CardState extends Flashcard {
|
interface CardState extends Flashcard {
|
||||||
skipCount: number
|
skipCount: number
|
||||||
wrongCount: number
|
wrongCount: number
|
||||||
isCompleted: boolean
|
isCompleted: boolean
|
||||||
originalOrder: number
|
originalOrder: number
|
||||||
synonyms?: string[]
|
synonyms: string[] // 改為必需的數組
|
||||||
difficultyLevelNumeric: number // 添加缺少的屬性
|
difficultyLevelNumeric: number // 必需屬性
|
||||||
|
exampleTranslation: string // 覆蓋 Flashcard 的可選定義,改為必需
|
||||||
|
updatedAt: string // 覆蓋 Flashcard 的可選定義,改為必需
|
||||||
|
primaryImageUrl: string | null // 確保類型匹配(null 而非 undefined)
|
||||||
}
|
}
|
||||||
|
|
||||||
interface QuizItem {
|
interface QuizItem {
|
||||||
|
|
@ -89,16 +92,18 @@ const generateQuizItemsFromFlashcards = (flashcards: Flashcard[]): QuizItem[] =>
|
||||||
let order = 0
|
let order = 0
|
||||||
|
|
||||||
flashcards.forEach((card) => {
|
flashcards.forEach((card) => {
|
||||||
// 轉換 Flashcard 為 CardState 格式
|
// 轉換 Flashcard 為 CardState 格式,確保所有屬性都有值
|
||||||
const cardState: CardState = {
|
const cardState: CardState = {
|
||||||
...card,
|
...card,
|
||||||
exampleTranslation: card.exampleTranslation || '', // 確保 exampleTranslation 不為 undefined
|
exampleTranslation: card.exampleTranslation || '', // 確保為 string,不是 undefined
|
||||||
|
updatedAt: card.updatedAt || card.createdAt, // 確保 updatedAt 為 string
|
||||||
|
primaryImageUrl: card.primaryImageUrl || null, // 確保為 null 而非 undefined
|
||||||
skipCount: 0,
|
skipCount: 0,
|
||||||
wrongCount: 0,
|
wrongCount: 0,
|
||||||
isCompleted: false,
|
isCompleted: false,
|
||||||
originalOrder: order / 2, // 原始詞卡的順序
|
originalOrder: order / 2, // 原始詞卡的順序
|
||||||
synonyms: [],
|
synonyms: [], // 確保為空數組而非 undefined
|
||||||
difficultyLevelNumeric: card.masteryLevel || 1 // 添加缺少的屬性
|
difficultyLevelNumeric: card.masteryLevel || 1 // 使用 masteryLevel 或預設值 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// 為每張詞卡生成兩種測驗模式
|
// 為每張詞卡生成兩種測驗模式
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue