feat: 建立Review Tests組件展示頁面

- 新增/review-tests測試頁面專門展示review-tests組件
- 導航欄添加🧪測試項目方便快速進入
- 實現Tab切換界面,每個測驗組件獨立展示
- 包含操作日誌系統追蹤組件互動行為
- 修正SentenceSpeakingTest組件props類型錯誤
- 提供完整模擬資料用於組件測試

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
鄭沛軒 2025-09-27 19:18:43 +08:00
parent 74932e58ff
commit f494331bdb
2 changed files with 221 additions and 0 deletions

View File

@ -0,0 +1,220 @@
'use client'
import { useState } from 'react'
import { Navigation } from '@/components/Navigation'
import {
FlipMemoryTest,
VocabChoiceTest,
SentenceFillTest,
SentenceReorderTest,
VocabListeningTest,
SentenceListeningTest,
SentenceSpeakingTest
} from '@/components/review/review-tests'
export default function ReviewTestsPage() {
const [logs, setLogs] = useState<string[]>([])
const [activeTab, setActiveTab] = useState('FlipMemoryTest')
// 測驗組件清單
const testComponents = [
{ id: 'FlipMemoryTest', name: '翻卡記憶測試', color: 'bg-blue-50' },
{ id: 'VocabChoiceTest', name: '詞彙選擇測試', color: 'bg-green-50' },
{ id: 'SentenceFillTest', name: '句子填空測試', color: 'bg-yellow-50' },
{ id: 'SentenceReorderTest', name: '句子重排測試', color: 'bg-purple-50' },
{ id: 'VocabListeningTest', name: '詞彙聽力測試', color: 'bg-red-50' },
{ id: 'SentenceListeningTest', name: '句子聽力測試', color: 'bg-indigo-50' },
{ id: 'SentenceSpeakingTest', name: '句子口說測試', color: 'bg-pink-50' }
]
// 添加日誌函數
const addLog = (message: string) => {
const timestamp = new Date().toLocaleTimeString()
setLogs(prev => [`[${activeTab}] [${timestamp}] ${message}`, ...prev.slice(0, 9)])
}
// 模擬資料
const mockCardData = {
word: "elaborate",
definition: "To explain something in more detail; to develop or present a theory, policy, or system in further detail",
example: "Could you elaborate on your proposal for the new marketing strategy?",
exampleTranslation: "你能詳細說明一下你對新行銷策略的提案嗎?",
pronunciation: "/ɪˈlæbərət/",
synonyms: ["explain", "detail", "expand", "clarify"],
difficultyLevel: "B2",
exampleImage: "https://via.placeholder.com/400x200?text=Marketing+Strategy"
}
// 選項題選項
const vocabChoiceOptions = ["elaborate", "celebrate", "collaborate", "deliberate"]
// 回調函數
const handleConfidenceSubmit = (level: number) => {
addLog(`FlipMemoryTest: 信心等級 ${level}`)
}
const handleAnswer = (answer: string) => {
addLog(`答案提交: ${answer}`)
}
const handleReportError = () => {
addLog('回報錯誤')
}
const handleImageClick = (image: string) => {
addLog(`圖片點擊: ${image}`)
}
return (
<div className="min-h-screen bg-gray-50">
<Navigation />
<div className="max-w-7xl mx-auto px-4 py-8">
{/* 頁面標題 */}
<div className="mb-8">
<h1 className="text-3xl font-bold text-gray-900 mb-2">Review Tests </h1>
</div>
{/* Tab 導航 */}
<div className="mb-8">
<div className="border-b border-gray-200">
<div className="flex space-x-8 overflow-x-auto">
{testComponents.map((component) => (
<button
key={component.id}
onClick={() => setActiveTab(component.id)}
className={`py-4 px-1 border-b-2 font-medium text-sm whitespace-nowrap transition-colors ${
activeTab === component.id
? 'border-blue-500 text-blue-600'
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
}`}
>
{component.name}
</button>
))}
</div>
</div>
</div>
{/* 當前測驗組件展示 */}
<div className="bg-white rounded-lg shadow-md overflow-hidden">
<div className={`px-6 py-4 border-b ${testComponents.find(c => c.id === activeTab)?.color}`}>
<h2 className="text-2xl font-semibold text-gray-900">{activeTab}</h2>
<p className="text-sm text-gray-600 mt-1">{testComponents.find(c => c.id === activeTab)?.name}</p>
</div>
<div className="p-8">
{/* 條件渲染當前選中的測驗組件 */}
{activeTab === 'FlipMemoryTest' && (
<FlipMemoryTest
word={mockCardData.word}
definition={mockCardData.definition}
example={mockCardData.example}
exampleTranslation={mockCardData.exampleTranslation}
pronunciation={mockCardData.pronunciation}
synonyms={mockCardData.synonyms}
difficultyLevel={mockCardData.difficultyLevel}
onConfidenceSubmit={handleConfidenceSubmit}
onReportError={handleReportError}
/>
)}
{activeTab === 'VocabChoiceTest' && (
<VocabChoiceTest
word={mockCardData.word}
definition={mockCardData.definition}
example={mockCardData.example}
exampleTranslation={mockCardData.exampleTranslation}
pronunciation={mockCardData.pronunciation}
difficultyLevel={mockCardData.difficultyLevel}
options={vocabChoiceOptions}
onAnswer={handleAnswer}
onReportError={handleReportError}
/>
)}
{activeTab === 'SentenceFillTest' && (
<SentenceFillTest
word={mockCardData.word}
definition={mockCardData.definition}
example={mockCardData.example}
exampleTranslation={mockCardData.exampleTranslation}
pronunciation={mockCardData.pronunciation}
difficultyLevel={mockCardData.difficultyLevel}
exampleImage={mockCardData.exampleImage}
onAnswer={handleAnswer}
onReportError={handleReportError}
onImageClick={handleImageClick}
/>
)}
{activeTab === 'SentenceReorderTest' && (
<SentenceReorderTest
word={mockCardData.word}
definition={mockCardData.definition}
example={mockCardData.example}
exampleTranslation={mockCardData.exampleTranslation}
difficultyLevel={mockCardData.difficultyLevel}
onAnswer={handleAnswer}
onReportError={handleReportError}
/>
)}
{activeTab === 'VocabListeningTest' && (
<VocabListeningTest
word={mockCardData.word}
definition={mockCardData.definition}
pronunciation={mockCardData.pronunciation}
difficultyLevel={mockCardData.difficultyLevel}
options={vocabChoiceOptions}
onAnswer={handleAnswer}
onReportError={handleReportError}
/>
)}
{activeTab === 'SentenceListeningTest' && (
<SentenceListeningTest
word={mockCardData.word}
example={mockCardData.example}
exampleTranslation={mockCardData.exampleTranslation}
difficultyLevel={mockCardData.difficultyLevel}
options={vocabChoiceOptions}
onAnswer={handleAnswer}
onReportError={handleReportError}
/>
)}
{activeTab === 'SentenceSpeakingTest' && (
<SentenceSpeakingTest
word={mockCardData.word}
example={mockCardData.example}
exampleTranslation={mockCardData.exampleTranslation}
difficultyLevel={mockCardData.difficultyLevel}
exampleImage={mockCardData.exampleImage}
onAnswer={handleAnswer}
onReportError={handleReportError}
onImageClick={handleImageClick}
/>
)}
</div>
</div>
{/* 操作日誌區域 */}
<div className="mt-8 bg-white rounded-lg shadow p-4">
<h3 className="font-semibold text-gray-900 mb-3"></h3>
<div className="space-y-1 max-h-32 overflow-y-auto">
{logs.length === 0 ? (
<p className="text-gray-500 text-sm"></p>
) : (
logs.map((log, index) => (
<div key={index} className="text-sm text-gray-600 font-mono">
{log}
</div>
))
)}
</div>
</div>
</div>
</div>
)
}

View File

@ -19,6 +19,7 @@ export function Navigation({ showExitLearning = false, onExitLearning }: Navigat
{ href: '/dashboard', label: '儀表板' },
{ href: '/flashcards', label: '詞卡' },
{ href: '/review', label: '複習' },
{ href: '/review-tests', label: '🧪 測試' },
{ href: '/generate', label: 'AI 生成' },
{ href: '/settings', label: '⚙️ 設定' }
]