dramaling-vocab-learning/frontend/app/review-design/page.tsx

277 lines
11 KiB
TypeScript

'use client'
import { useState, useEffect } from 'react'
import { Navigation } from '@/components/Navigation'
import {
FlipMemoryTest,
VocabChoiceTest,
SentenceFillTest,
SentenceReorderTest,
VocabListeningTest,
SentenceListeningTest,
SentenceSpeakingTest
} from '@/components/review/review-tests'
import exampleData from './example-data.json'
export default function ReviewTestsPage() {
const [logs, setLogs] = useState<string[]>([])
const [activeTab, setActiveTab] = useState('FlipMemoryTest')
const [currentCardIndex, setCurrentCardIndex] = useState(0)
// 測驗組件清單
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)])
}
// 從 API 響應格式獲取當前卡片資料
const flashcardsData = exampleData.data || []
const currentCard = flashcardsData[currentCardIndex] || flashcardsData[0]
// 轉換為組件所需格式
const mockCardData = currentCard ? {
word: currentCard.word,
definition: currentCard.definition,
example: currentCard.example,
filledQuestionText: currentCard.filledQuestionText,
exampleTranslation: currentCard.exampleTranslation,
pronunciation: currentCard.pronunciation,
synonyms: currentCard.synonyms || [],
difficultyLevel: currentCard.difficultyLevel,
translation: currentCard.translation,
// 從 flashcardExampleImages 提取圖片URL
exampleImage: currentCard.flashcardExampleImages?.[0]?.exampleImage ?
`http://localhost:5008/images/examples/${currentCard.flashcardExampleImages[0].exampleImage.relativePath}` :
undefined
} : {
word: "loading...",
definition: "Loading...",
example: "Loading...",
filledQuestionText: undefined,
exampleTranslation: "載入中...",
pronunciation: "",
difficultyLevel: "A1",
translation: "載入中",
exampleImage: undefined
}
// 選項題選項 - 從資料中生成
const generateVocabChoiceOptions = () => {
if (!currentCard) return ["loading"]
const correctAnswer = currentCard.word
const otherWords = flashcardsData
.filter(card => card.word !== correctAnswer)
.slice(0, 3)
.map(card => card.word)
return [correctAnswer, ...otherWords].sort(() => Math.random() - 0.5)
}
const vocabChoiceOptions = generateVocabChoiceOptions()
// 回調函數
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-gradient-to-br from-blue-50 to-indigo-100">
<Navigation />
<div className="max-w-4xl mx-auto px-4 py-8">
{/* 頁面標題 */}
<div className="mb-8">
<h1 className="text-3xl font-bold text-gray-900 mb-2">Review </h1>
<p className="text-gray-600"> review-tests UI </p>
{/* 卡片切換控制 */}
<div className="mt-4 flex items-center gap-4">
<button
onClick={() => setCurrentCardIndex(Math.max(0, currentCardIndex - 1))}
disabled={currentCardIndex === 0}
className="px-3 py-1 bg-gray-500 text-white rounded disabled:opacity-50"
>
</button>
<span className="text-sm text-gray-600">
{currentCardIndex + 1} / {flashcardsData.length} - {currentCard?.word || 'loading'}
</span>
<button
onClick={() => setCurrentCardIndex(Math.min(flashcardsData.length - 1, currentCardIndex + 1))}
disabled={currentCardIndex >= flashcardsData.length - 1}
className="px-3 py-1 bg-gray-500 text-white rounded disabled:opacity-50"
>
</button>
</div>
</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="mb-8">
<div className="mb-6">
<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>
{/* 條件渲染當前選中的測驗組件 */}
{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
cardData={{
...mockCardData,
id: currentCard?.id || `card-${currentCardIndex}`,
synonyms: mockCardData.synonyms || []
}}
options={vocabChoiceOptions}
onAnswer={handleAnswer}
onReportError={handleReportError}
/>
)}
{activeTab === 'SentenceFillTest' && (
<SentenceFillTest
word={mockCardData.word}
definition={mockCardData.definition}
example={mockCardData.example}
filledQuestionText={mockCardData.filledQuestionText}
exampleTranslation={mockCardData.exampleTranslation}
pronunciation={mockCardData.pronunciation}
difficultyLevel={mockCardData.difficultyLevel}
exampleImage={mockCardData.exampleImage}
onAnswer={handleAnswer}
onReportError={handleReportError}
onImageClick={handleImageClick}
/>
)}
{activeTab === 'SentenceReorderTest' && (
<SentenceReorderTest
cardData={{
...mockCardData,
id: currentCard?.id || `card-${currentCardIndex}`,
synonyms: mockCardData.synonyms || []
}}
exampleImage={mockCardData.exampleImage}
onAnswer={handleAnswer}
onReportError={handleReportError}
onImageClick={handleImageClick}
/>
)}
{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}
exampleImage={mockCardData.exampleImage}
onAnswer={handleAnswer}
onReportError={handleReportError}
onImageClick={handleImageClick}
/>
)}
{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>
)
}