248 lines
7.3 KiB
TypeScript
248 lines
7.3 KiB
TypeScript
import { useEffect } from 'react'
|
|
import { useReviewSessionStore } from '@/store/useReviewSessionStore'
|
|
import { useTestQueueStore } from '@/store/useTestQueueStore'
|
|
import { useTestResultStore } from '@/store/useTestResultStore'
|
|
import { useUIStore } from '@/store/useUIStore'
|
|
import {
|
|
FlipMemoryTest,
|
|
VocabChoiceTest,
|
|
SentenceFillTest,
|
|
SentenceReorderTest,
|
|
VocabListeningTest,
|
|
SentenceListeningTest,
|
|
SentenceSpeakingTest
|
|
} from './review-tests'
|
|
|
|
interface TestRunnerProps {
|
|
className?: string
|
|
}
|
|
|
|
export const ReviewRunner: React.FC<TestRunnerProps> = ({ className }) => {
|
|
const { currentCard, error } = useReviewSessionStore()
|
|
const { currentMode, testItems, currentTestIndex, markTestCompleted, goToNextTest } = useTestQueueStore()
|
|
const { updateScore, recordTestResult } = useTestResultStore()
|
|
|
|
const {
|
|
openReportModal,
|
|
openImageModal
|
|
} = useUIStore()
|
|
|
|
// 處理答題
|
|
const handleAnswer = async (answer: string, confidenceLevel?: number) => {
|
|
if (!currentCard) return
|
|
|
|
// 檢查答案正確性
|
|
const isCorrect = checkAnswer(answer, currentCard, currentMode)
|
|
|
|
// 更新分數
|
|
updateScore(isCorrect)
|
|
|
|
// 記錄到後端
|
|
const success = await recordTestResult({
|
|
flashcardId: currentCard.id,
|
|
testType: currentMode,
|
|
isCorrect,
|
|
userAnswer: answer,
|
|
confidenceLevel,
|
|
responseTimeMs: 2000
|
|
})
|
|
|
|
if (success) {
|
|
// 標記測驗為完成
|
|
markTestCompleted(currentTestIndex)
|
|
|
|
// 延遲進入下一個測驗
|
|
setTimeout(() => {
|
|
goToNextTest()
|
|
}, 1500)
|
|
}
|
|
}
|
|
|
|
// 檢查答案正確性
|
|
const checkAnswer = (answer: string, card: any, mode: string): boolean => {
|
|
switch (mode) {
|
|
case 'flip-memory':
|
|
return true // 翻卡記憶沒有對錯,只有信心等級
|
|
|
|
case 'vocab-choice':
|
|
case 'vocab-listening':
|
|
return answer === card.word
|
|
|
|
case 'sentence-fill':
|
|
return answer.toLowerCase().trim() === card.word.toLowerCase()
|
|
|
|
case 'sentence-reorder':
|
|
case 'sentence-listening':
|
|
return answer.toLowerCase().trim() === card.example.toLowerCase().trim()
|
|
|
|
case 'sentence-speaking':
|
|
return true // 口說測驗通常算正確
|
|
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
// 生成測驗選項
|
|
const generateOptions = (card: any, mode: string): string[] => {
|
|
// 這裡應該根據測驗類型生成對應的選項
|
|
// 暫時返回簡單的佔位符
|
|
switch (mode) {
|
|
case 'vocab-choice':
|
|
case 'vocab-listening':
|
|
return [card.word, '其他選項1', '其他選項2', '其他選項3'].sort(() => Math.random() - 0.5)
|
|
|
|
case 'sentence-listening':
|
|
return [
|
|
card.example,
|
|
'其他例句選項1',
|
|
'其他例句選項2',
|
|
'其他例句選項3'
|
|
].sort(() => Math.random() - 0.5)
|
|
|
|
default:
|
|
return []
|
|
}
|
|
}
|
|
|
|
if (error) {
|
|
return (
|
|
<div className="text-center py-8">
|
|
<div className="bg-red-50 border border-red-200 rounded-lg p-6">
|
|
<h3 className="text-lg font-semibold text-red-700 mb-2">發生錯誤</h3>
|
|
<p className="text-red-600">{error}</p>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
if (!currentCard) {
|
|
return (
|
|
<div className="text-center py-8">
|
|
<div className="text-gray-500">載入測驗中...</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
// 共同的 props
|
|
const cardData = {
|
|
id: currentCard.id,
|
|
word: currentCard.word,
|
|
definition: currentCard.definition,
|
|
example: currentCard.example,
|
|
translation: currentCard.translation || '',
|
|
exampleTranslation: currentCard.translation || '',
|
|
pronunciation: currentCard.pronunciation,
|
|
difficultyLevel: currentCard.difficultyLevel || 'A2',
|
|
exampleImage: currentCard.exampleImage,
|
|
synonyms: currentCard.synonyms || []
|
|
}
|
|
|
|
const commonProps = {
|
|
cardData,
|
|
onAnswer: handleAnswer,
|
|
onReportError: () => openReportModal(currentCard)
|
|
}
|
|
|
|
// 渲染對應的測驗組件
|
|
switch (currentMode) {
|
|
case 'flip-memory':
|
|
return (
|
|
<FlipMemoryTest
|
|
word={cardData.word}
|
|
definition={cardData.definition}
|
|
example={cardData.example}
|
|
exampleTranslation={cardData.exampleTranslation}
|
|
pronunciation={cardData.pronunciation}
|
|
synonyms={cardData.synonyms}
|
|
difficultyLevel={cardData.difficultyLevel}
|
|
onConfidenceSubmit={(level) => handleAnswer('', level)}
|
|
onReportError={() => openReportModal(currentCard)}
|
|
/>
|
|
)
|
|
|
|
case 'vocab-choice':
|
|
return (
|
|
<VocabChoiceTest
|
|
{...commonProps}
|
|
options={generateOptions(currentCard, currentMode)}
|
|
/>
|
|
)
|
|
|
|
case 'sentence-fill':
|
|
return (
|
|
<SentenceFillTest
|
|
word={cardData.word}
|
|
definition={cardData.definition}
|
|
example={cardData.example}
|
|
exampleTranslation={cardData.exampleTranslation}
|
|
pronunciation={cardData.pronunciation}
|
|
synonyms={cardData.synonyms}
|
|
difficultyLevel={cardData.difficultyLevel}
|
|
exampleImage={cardData.exampleImage}
|
|
onAnswer={handleAnswer}
|
|
onReportError={() => openReportModal(currentCard)}
|
|
onImageClick={openImageModal}
|
|
/>
|
|
)
|
|
|
|
case 'sentence-reorder':
|
|
return (
|
|
<SentenceReorderTest
|
|
{...commonProps}
|
|
exampleImage={cardData.exampleImage}
|
|
onImageClick={openImageModal}
|
|
/>
|
|
)
|
|
|
|
case 'vocab-listening':
|
|
return (
|
|
<VocabListeningTest
|
|
word={cardData.word}
|
|
definition={cardData.definition}
|
|
pronunciation={cardData.pronunciation}
|
|
difficultyLevel={cardData.difficultyLevel}
|
|
options={generateOptions(currentCard, currentMode)}
|
|
onAnswer={handleAnswer}
|
|
onReportError={() => openReportModal(currentCard)}
|
|
/>
|
|
)
|
|
|
|
case 'sentence-listening':
|
|
return (
|
|
<SentenceListeningTest
|
|
word={cardData.word}
|
|
example={cardData.example}
|
|
exampleTranslation={cardData.exampleTranslation}
|
|
difficultyLevel={cardData.difficultyLevel}
|
|
options={generateOptions(currentCard, currentMode)}
|
|
onAnswer={handleAnswer}
|
|
onReportError={() => openReportModal(currentCard)}
|
|
/>
|
|
)
|
|
|
|
case 'sentence-speaking':
|
|
return (
|
|
<SentenceSpeakingTest
|
|
word={cardData.word}
|
|
example={cardData.example}
|
|
exampleTranslation={cardData.exampleTranslation}
|
|
difficultyLevel={cardData.difficultyLevel}
|
|
exampleImage={cardData.exampleImage}
|
|
onAnswer={handleAnswer}
|
|
onReportError={() => openReportModal(currentCard)}
|
|
onImageClick={openImageModal}
|
|
/>
|
|
)
|
|
|
|
default:
|
|
return (
|
|
<div className="text-center py-8">
|
|
<div className="bg-yellow-50 border border-yellow-200 rounded-lg p-6">
|
|
<h3 className="text-lg font-semibold text-yellow-700 mb-2">未實現的測驗類型</h3>
|
|
<p className="text-yellow-600">測驗類型 "{currentMode}" 尚未實現</p>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
} |