import { useEffect, useState, useCallback } from 'react' import { useReviewSessionStore } from '@/store/useReviewSessionStore' import { useTestQueueStore } from '@/store/useTestQueueStore' import { useTestResultStore } from '@/store/useTestResultStore' import { useUIStore } from '@/store/useUIStore' import { SmartNavigationController } from './NavigationController' import { ProgressBar } from './ProgressBar' import { mockFlashcards } from '@/data/mockTestData' import { FlipMemoryTest, VocabChoiceTest, SentenceFillTest, SentenceReorderTest, VocabListeningTest, SentenceListeningTest, SentenceSpeakingTest } from './review-tests' interface TestRunnerProps { className?: string } export const ReviewRunner: React.FC = ({ className }) => { const { currentCard, error } = useReviewSessionStore() const { currentMode, testItems, currentTestIndex, markTestCompleted, goToNextTest, skipCurrentTest } = useTestQueueStore() const { updateScore, recordTestResult, score } = useTestResultStore() // 答題狀態管理 const [hasAnswered, setHasAnswered] = useState(false) const [isProcessingAnswer, setIsProcessingAnswer] = useState(false) const { openReportModal, openImageModal } = useUIStore() // 重置答題狀態(切換測驗時) useEffect(() => { setHasAnswered(false) setIsProcessingAnswer(false) }, [currentTestIndex, currentMode]) // 處理答題(只記錄答案,不進行導航) const handleAnswer = useCallback(async (answer: string, confidenceLevel?: number) => { if (!currentCard || hasAnswered || isProcessingAnswer) return setIsProcessingAnswer(true) try { // 檢查答案正確性 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) // 設定已答題狀態(啟用導航按鈕) setHasAnswered(true) // 如果答錯,將題目移到隊列最後(優先級 20) if (!isCorrect && currentMode !== 'flip-memory') { // TODO: 實現答錯重排邏輯 console.log('答錯,將重新排入隊列') } } } catch (error) { console.error('答題處理失敗:', error) } finally { setIsProcessingAnswer(false) } }, [currentCard, hasAnswered, isProcessingAnswer, currentMode, updateScore, recordTestResult, markTestCompleted, currentTestIndex]) // 檢查答案正確性 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 [] } } // 處理跳過 const handleSkip = useCallback(() => { if (hasAnswered) return // 已答題後不能跳過 skipCurrentTest() // 重置狀態準備下一題 setHasAnswered(false) setIsProcessingAnswer(false) }, [hasAnswered, skipCurrentTest]) // 處理繼續 const handleContinue = useCallback(() => { if (!hasAnswered) return // 未答題不能繼續 goToNextTest() // 重置狀態準備下一題 setHasAnswered(false) setIsProcessingAnswer(false) }, [hasAnswered, goToNextTest]) // 測驗內容渲染函數 (使用 mock 數據) const renderTestContentWithMockData = (mockCardData: any, testType: string, options: string[]) => { const mockCommonProps = { cardData: mockCardData, onAnswer: handleAnswer, onReportError: () => console.log('Mock report error') } switch (testType) { case 'flip-memory': return ( handleAnswer('', level)} disabled={isProcessingAnswer} /> ) case 'vocab-choice': return ( ) case 'sentence-fill': return ( ) case 'sentence-reorder': return ( console.log('Mock image click:', image)} disabled={isProcessingAnswer} /> ) case 'vocab-listening': return ( ) case 'sentence-listening': return ( console.log('Mock image click:', image)} disabled={isProcessingAnswer} /> ) case 'sentence-speaking': return ( console.log('Mock image click:', image)} disabled={isProcessingAnswer} /> ) default: return (

未實現的測驗類型

測驗類型 "{testType}" 尚未實現

) } } if (error) { return (

發生錯誤

{error}

) } if (!currentCard) { // 檢查是否有測試隊列但沒有 currentCard (測試模式) if (testItems.length > 0 && currentTestIndex < testItems.length) { const currentTest = testItems[currentTestIndex] const mockCard = mockFlashcards.find(card => card.id === currentTest.cardId) if (mockCard) { // 使用 mock 數據創建 cardData const mockCardData = { id: mockCard.id, word: mockCard.word, definition: mockCard.definition, example: mockCard.example, translation: mockCard.translation, exampleTranslation: mockCard.exampleTranslation, pronunciation: mockCard.pronunciation, difficultyLevel: mockCard.difficultyLevel, exampleImage: mockCard.exampleImage, synonyms: mockCard.synonyms } // 生成測驗選項 const mockOptions = generateOptions(mockCard, currentTest.testType) return (
{/* 測驗內容 */}
{renderTestContentWithMockData(mockCardData, currentTest.testType, mockOptions)}
{/* 智能導航控制器 */}
) } } return (
載入測驗中...
) } // 共同的 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) } // 測驗內容渲染函數 const renderTestContent = () => { // 渲染對應的測驗組件(不包含導航) switch (currentMode) { case 'flip-memory': return ( handleAnswer('', level)} disabled={isProcessingAnswer} /> ) case 'vocab-choice': return ( ) case 'sentence-fill': return ( ) case 'sentence-reorder': return ( ) case 'vocab-listening': return ( ) case 'sentence-listening': return ( ) case 'sentence-speaking': return ( ) default: return (

未實現的測驗類型

測驗類型 "{currentMode}" 尚未實現

) } } return (
{/* 進度條 (僅在測試模式顯示) */} {testItems.length > 0 && (
item.isSkipped).length} />
)} {/* 測驗內容 */}
{renderTestContent()}
{/* 智能導航控制器 */}
) }