dramaling-vocab-learning/frontend/components/review/ReviewRunner.tsx

218 lines
5.8 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
{...commonProps}
onConfidenceSubmit={(level) => handleAnswer('', level)}
/>
)
case 'vocab-choice':
return (
<VocabChoiceTest
{...commonProps}
options={generateOptions(currentCard, currentMode)}
/>
)
case 'sentence-fill':
return (
<SentenceFillTest
{...commonProps}
/>
)
case 'sentence-reorder':
return (
<SentenceReorderTest
{...commonProps}
exampleImage={cardData.exampleImage}
onImageClick={openImageModal}
/>
)
case 'vocab-listening':
return (
<VocabListeningTest
{...commonProps}
options={generateOptions(currentCard, currentMode)}
/>
)
case 'sentence-listening':
return (
<SentenceListeningTest
{...commonProps}
options={generateOptions(currentCard, currentMode)}
exampleImage={cardData.exampleImage}
onImageClick={openImageModal}
/>
)
case 'sentence-speaking':
return (
<SentenceSpeakingTest
{...commonProps}
exampleImage={cardData.exampleImage}
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>
)
}
}