272 lines
10 KiB
TypeScript
272 lines
10 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,
|
|
exampleTranslation: currentCard.exampleTranslation,
|
|
pronunciation: currentCard.pronunciation,
|
|
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...",
|
|
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={[]}
|
|
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}
|
|
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}
|
|
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>
|
|
)
|
|
} |