'use client' import { useState, useEffect, useRef, useLayoutEffect } from 'react' import { useRouter } from 'next/navigation' import { Navigation } from '@/components/Navigation' import AudioPlayer from '@/components/AudioPlayer' import VoiceRecorder from '@/components/VoiceRecorder' import LearningComplete from '@/components/LearningComplete' import ReviewTypeIndicator from '@/components/review/ReviewTypeIndicator' import MasteryIndicator from '@/components/review/MasteryIndicator' import { flashcardsService, type Flashcard } from '@/lib/services/flashcards' import { calculateCurrentMastery, getReviewTypesByDifficulty } from '@/lib/utils/masteryCalculator' // 測驗項目接口 interface TestItem { id: string; // 唯一ID: cardId + testType cardId: string; // 所屬詞卡ID word: string; // 詞卡單字 testType: string; // 測驗類型 (flip-memory, vocab-choice, etc.) testName: string; // 測驗中文名稱 isCompleted: boolean; // 是否已完成 isCurrent: boolean; // 是否為當前測驗 order: number; // 執行順序 (1-8) } // 詞卡測驗分組接口 interface CardTestGroup { cardId: string; word: string; context: string; tests: TestItem[]; } // 擴展的Flashcard接口,包含智能複習需要的欄位 interface ExtendedFlashcard extends Omit { nextReviewDate?: string; // 下次復習日期 (可選) currentInterval?: number; // 當前間隔天數 isOverdue?: boolean; // 是否逾期 overdueDays?: number; // 逾期天數 baseMasteryLevel?: number; // 基礎熟悉度 lastReviewDate?: string; // 最後復習日期 synonyms?: string[]; // 同義詞 exampleImage?: string; // 例句圖片 // 注意:userLevel和wordLevel已移除,改用即時CEFR轉換 } // 單個測驗結果接口 interface TestResult { testType: string; // 測驗類型 isCorrect: boolean; // 是否正確 userAnswer?: string; // 用戶答案 confidenceLevel?: number; // 信心等級 (1-5, 用於flip-memory) responseTimeMs: number; // 答題時間 completedAt: Date; // 完成時間 } // 詞卡複習會話接口 interface CardReviewSession { cardId: string; // 詞卡ID word: string; // 詞卡單字 plannedTests: string[]; // 預定的測驗類型列表 completedTests: TestResult[]; // 已完成的測驗結果 startedAt: Date; // 開始時間 isCompleted: boolean; // 是否完成所有測驗 } export default function LearnPage() { const router = useRouter() const [mounted, setMounted] = useState(false) // 智能複習狀態 const [currentCard, setCurrentCard] = useState(null) const [dueCards, setDueCards] = useState([]) const [currentCardIndex, setCurrentCardIndex] = useState(0) const [isLoadingCard, setIsLoadingCard] = useState(false) // 複習模式狀態 (系統自動選擇) const [mode, setMode] = useState<'flip-memory' | 'vocab-choice' | 'vocab-listening' | 'sentence-listening' | 'sentence-fill' | 'sentence-reorder' | 'sentence-speaking'>('flip-memory') const [isAutoSelecting, setIsAutoSelecting] = useState(true) // 答題狀態 const [score, setScore] = useState({ correct: 0, total: 0 }) const [selectedAnswer, setSelectedAnswer] = useState(null) const [showResult, setShowResult] = useState(false) const [fillAnswer, setFillAnswer] = useState('') const [showHint, setShowHint] = useState(false) const [isFlipped, setIsFlipped] = useState(false) // 測驗進度狀態 const [totalTests, setTotalTests] = useState(0) // 所有測驗總數 const [completedTests, setCompletedTests] = useState(0) // 已完成測驗數 const [testItems, setTestItems] = useState([]) // 測驗項目列表 const [currentTestItemIndex, setCurrentTestItemIndex] = useState(0) // 當前測驗項目索引 // 詞卡複習會話狀態 const [cardReviewSessions, setCardReviewSessions] = useState>(new Map()) const [currentCardSession, setCurrentCardSession] = useState(null) const [completedCards, setCompletedCards] = useState(0) // 已完成復習的詞卡數 // UI狀態 const [modalImage, setModalImage] = useState(null) const [showReportModal, setShowReportModal] = useState(false) const [reportReason, setReportReason] = useState('') const [reportingCard, setReportingCard] = useState(null) const [showComplete, setShowComplete] = useState(false) const [showNoDueCards, setShowNoDueCards] = useState(false) const [showTaskListModal, setShowTaskListModal] = useState(false) const [cardHeight, setCardHeight] = useState(400) // 題型特定狀態 const [quizOptions, setQuizOptions] = useState([]) const [sentenceOptions, setSentenceOptions] = useState([]) // 例句重組狀態 const [shuffledWords, setShuffledWords] = useState([]) const [arrangedWords, setArrangedWords] = useState([]) const [reorderResult, setReorderResult] = useState(null) // Refs for measuring card content heights const cardFrontRef = useRef(null) const cardBackRef = useRef(null) const cardContainerRef = useRef(null) // Calculate optimal card height based on content (only when card changes) const calculateCardHeight = () => { if (!cardFrontRef.current || !cardBackRef.current) return 400; // Get the scroll heights to measure actual content const frontHeight = cardFrontRef.current.scrollHeight; const backHeight = cardBackRef.current.scrollHeight; console.log('Heights calculated:', { frontHeight, backHeight }); // Debug log // Use the maximum height with padding const maxHeight = Math.max(frontHeight, backHeight); const paddedHeight = maxHeight + 40; // Add padding for visual spacing // Ensure minimum height for visual consistency return Math.max(paddedHeight, 450); }; // Update card height only when card content changes (not on flip) useLayoutEffect(() => { if (mounted && cardFrontRef.current && cardBackRef.current) { // Wait for DOM to be fully rendered const timer = setTimeout(() => { const newHeight = calculateCardHeight(); setCardHeight(newHeight); }, 50); return () => clearTimeout(timer); } }, [currentCardIndex, mounted]); // Client-side mounting useEffect(() => { setMounted(true) loadDueCards() // 載入到期詞卡 }, []) // 載入到期詞卡列表 const loadDueCards = async () => { try { setIsLoadingCard(true) console.log('🔍 開始載入到期詞卡...'); // 完全使用後端API數據 const apiResult = await flashcardsService.getDueFlashcards(50); console.log('📡 API回應結果:', apiResult); if (apiResult.success && apiResult.data && apiResult.data.length > 0) { const cardsToUse = apiResult.data; console.log('✅ 載入後端API數據成功:', cardsToUse.length, '張詞卡'); console.log('📋 詞卡列表:', cardsToUse.map(c => c.word)); // 查詢已完成的測驗 const cardIds = cardsToUse.map(c => c.id); let completedTests: any[] = []; try { const completedTestsResult = await flashcardsService.getCompletedTests(cardIds); if (completedTestsResult.success && completedTestsResult.data) { completedTests = completedTestsResult.data; console.log('📊 已完成測驗:', completedTests.length, '個'); } else { console.log('⚠️ 查詢已完成測驗失敗,使用空列表:', completedTestsResult.error); } } catch (error) { console.error('💥 查詢已完成測驗異常:', error); console.log('📝 繼續使用空的已完成測驗列表'); } // 計算每張詞卡剩餘的測驗 const userCEFR = localStorage.getItem('userEnglishLevel') || 'A2'; let remainingTestItems: TestItem[] = []; let order = 1; cardsToUse.forEach(card => { const wordCEFR = card.difficultyLevel || 'A2'; const allTestTypes = getReviewTypesByCEFR(userCEFR, wordCEFR); // 找出該詞卡已完成的測驗類型 const completedTestTypes = completedTests .filter(ct => ct.flashcardId === card.id) .map(ct => ct.testType); // 計算剩餘未完成的測驗類型 const remainingTestTypes = allTestTypes.filter(testType => !completedTestTypes.includes(testType) ); console.log(`🎯 詞卡 ${card.word}: 總共${allTestTypes.length}個測驗, 已完成${completedTestTypes.length}個, 剩餘${remainingTestTypes.length}個`); // 為剩餘的測驗創建測驗項目 remainingTestTypes.forEach(testType => { remainingTestItems.push({ id: `${card.id}-${testType}`, cardId: card.id, word: card.word, testType, testName: getModeLabel(testType), isCompleted: false, isCurrent: false, order }); order++; }); }); if (remainingTestItems.length === 0) { console.log('🎉 所有測驗都已完成!'); setShowComplete(true); return; } console.log('📝 剩餘測驗項目:', remainingTestItems.length, '個'); // 設置狀態 setTotalTests(remainingTestItems.length); setTestItems(remainingTestItems); setCurrentTestItemIndex(0); setCompletedTests(0); // 找到第一個測驗項目對應的詞卡 const firstTestItem = remainingTestItems[0]; const firstCard = cardsToUse.find(c => c.id === firstTestItem.cardId); if (firstCard && firstTestItem) { setCurrentCard(firstCard); setCurrentCardIndex(cardsToUse.findIndex(c => c.id === firstCard.id)); // 設置測驗模式為第一個測驗的類型 const modeMapping: { [key: string]: typeof mode } = { 'flip-memory': 'flip-memory', 'vocab-choice': 'vocab-choice', 'vocab-listening': 'vocab-listening', 'sentence-fill': 'sentence-fill', 'sentence-reorder': 'sentence-reorder', 'sentence-speaking': 'sentence-speaking', 'sentence-listening': 'sentence-listening' }; const selectedMode = modeMapping[firstTestItem.testType] || 'flip-memory'; setMode(selectedMode); setIsAutoSelecting(false); // 標記第一個測驗項目為當前狀態 setTestItems(prev => prev.map((item, index) => index === 0 ? { ...item, isCurrent: true } : item ) ); console.log(`🎯 恢復到未完成測驗: ${firstCard.word} - ${firstTestItem.testType}`); } } else { // 沒有到期詞卡 console.log('❌ API回應:', { success: apiResult.success, dataExists: !!apiResult.data, dataLength: apiResult.data?.length, error: apiResult.error }); setDueCards([]); setCurrentCard(null); setShowNoDueCards(true); } } catch (error) { console.error('💥 載入到期詞卡失敗:', error); setDueCards([]); setCurrentCard(null); setShowNoDueCards(true); } finally { setIsLoadingCard(false); } } // 智能載入下一張卡片並自動選擇模式 const loadNextCardWithAutoMode = async (cardIndex: number) => { try { setIsAutoSelecting(true); // 等待dueCards載入完成 if (dueCards.length === 0) { console.log('等待詞卡載入...'); return; } const card = dueCards[cardIndex]; if (!card) { setShowComplete(true); setIsAutoSelecting(false); return; } setCurrentCard(card); setCurrentCardIndex(cardIndex); // 系統自動選擇最適合的複習模式 const selectedMode = await selectOptimalReviewMode(card); setMode(selectedMode); // 重置所有答題狀態 resetAllStates(); console.log(`載入卡片: ${card.word}, 選擇模式: ${selectedMode}`); } catch (error) { console.error('載入卡片失敗:', error); } finally { setIsAutoSelecting(false); } } // 系統自動選擇最適合的複習模式 const selectOptimalReviewMode = async (card: ExtendedFlashcard): Promise => { try { // 使用CEFR字符串進行智能選擇 const userCEFRLevel = localStorage.getItem('userEnglishLevel') || 'A2'; const wordCEFRLevel = card.difficultyLevel || 'A2'; console.log(`CEFR智能選擇: 用戶${userCEFRLevel} vs 詞彙${wordCEFRLevel}`); const apiResult = await flashcardsService.getOptimalReviewMode(card.id, userCEFRLevel, wordCEFRLevel); if (apiResult.success && apiResult.data?.selectedMode) { const selectedMode = apiResult.data.selectedMode; console.log(`後端智能選擇: ${selectedMode}`); // 映射到前端模式名稱 const modeMapping: { [key: string]: typeof mode } = { 'flip-memory': 'flip-memory', 'vocab-choice': 'vocab-choice', 'vocab-listening': 'vocab-listening', 'sentence-fill': 'sentence-fill', 'sentence-reorder': 'sentence-reorder', 'sentence-speaking': 'sentence-speaking', 'sentence-listening': 'sentence-listening' }; return modeMapping[selectedMode] || 'flip-memory'; } else { console.log('後端API失敗,使用前端邏輯'); } } catch (error) { console.error('智能選擇API錯誤:', error); } // 備用: 使用前端CEFR邏輯 const userCEFRLevel = localStorage.getItem('userEnglishLevel') || 'A2'; const wordCEFRLevel = card.difficultyLevel || 'A2'; const availableModes = getReviewTypesByCEFR(userCEFRLevel, wordCEFRLevel); const modeMapping: { [key: string]: typeof mode } = { 'flip-memory': 'flip-memory', 'vocab-choice': 'vocab-choice', 'vocab-listening': 'vocab-listening', 'sentence-fill': 'sentence-fill', 'sentence-reorder': 'sentence-reorder', 'sentence-speaking': 'sentence-speaking', 'sentence-listening': 'sentence-listening' }; const selectedType = availableModes[0] || 'flip-memory'; console.log(`前端CEFR邏輯選擇: ${selectedType}`); return modeMapping[selectedType] || 'flip-memory'; } // 前端CEFR備用選擇邏輯 const getReviewTypesByCEFR = (userCEFR: string, wordCEFR: string): string[] => { const userLevel = getCEFRToLevel(userCEFR); const wordLevel = getCEFRToLevel(wordCEFR); const difficulty = wordLevel - userLevel; if (userCEFR === 'A1') { return ['flip-memory', 'vocab-choice', 'vocab-listening']; } else if (difficulty < -10) { return ['sentence-reorder', 'sentence-fill']; } else if (difficulty >= -10 && difficulty <= 10) { return ['sentence-fill', 'sentence-reorder', 'sentence-speaking']; } else { return ['flip-memory', 'vocab-choice']; } } // CEFR轉換為數值 (前端計算用) const getCEFRToLevel = (cefr: string): number => { const mapping: { [key: string]: number } = { 'A1': 20, 'A2': 35, 'B1': 50, 'B2': 65, 'C1': 80, 'C2': 95 }; return mapping[cefr] || 50; } // 計算每張詞卡的測驗數量 const calculateTestsForCard = (userCEFR: string, wordCEFR: string): number => { const userLevel = getCEFRToLevel(userCEFR); const wordLevel = getCEFRToLevel(wordCEFR); const difficulty = wordLevel - userLevel; if (userCEFR === 'A1') { return 3; // A1學習者:翻卡、選擇、聽力 } else if (difficulty < -10) { return 2; // 簡單詞彙:填空、重組 } else if (difficulty >= -10 && difficulty <= 10) { return 3; // 適中詞彙:填空、重組、口說 } else { return 2; // 困難詞彙:翻卡、選擇 } } // 取得當前學習情境 const getCurrentContext = (userCEFR: string, wordCEFR: string): string => { const userLevel = getCEFRToLevel(userCEFR); const wordLevel = getCEFRToLevel(wordCEFR); const difficulty = wordLevel - userLevel; if (userCEFR === 'A1') return 'A1學習者'; if (difficulty < -10) return '簡單詞彙'; if (difficulty >= -10 && difficulty <= 10) return '適中詞彙'; return '困難詞彙'; } // 生成完整四情境對照表數據 const generateContextTable = (currentUserCEFR: string, currentWordCEFR: string) => { const currentContext = getCurrentContext(currentUserCEFR, currentWordCEFR); const contexts = [ { type: 'A1學習者', icon: '🛡️', reviewTypes: ['🔄翻卡記憶', '✅詞彙選擇', '🎧詞彙聽力'], purpose: '建立基礎信心', condition: '用戶等級 = A1', description: '初學者保護機制,使用最基礎的3種題型' }, { type: '簡單詞彙', icon: '🎯', reviewTypes: ['✏️例句填空', '🔀例句重組'], purpose: '應用練習', condition: '用戶等級 > 詞彙等級', description: '詞彙對您較簡單,重點練習拼寫和語法應用' }, { type: '適中詞彙', icon: '⚖️', reviewTypes: ['✏️例句填空', '🔀例句重組', '🗣️例句口說'], purpose: '全方位練習', condition: '用戶等級 ≈ 詞彙等級', description: '詞彙難度適中,進行聽說讀寫全方位練習' }, { type: '困難詞彙', icon: '📚', reviewTypes: ['🔄翻卡記憶', '✅詞彙選擇'], purpose: '基礎重建', condition: '用戶等級 < 詞彙等級', description: '詞彙對您較困難,回歸基礎重建記憶' } ]; return contexts.map(context => ({ ...context, isCurrent: context.type === currentContext })); } // 取得題型圖標 const getModeIcon = (mode: string): string => { const icons: { [key: string]: string } = { 'flip-memory': '🔄', 'vocab-choice': '✅', 'vocab-listening': '🎧', 'sentence-listening': '👂', 'sentence-fill': '✏️', 'sentence-reorder': '🔀', 'sentence-speaking': '🗣️' }; return icons[mode] || '📝'; } // 取得題型中文名稱 const getModeLabel = (mode: string): string => { const labels: { [key: string]: string } = { 'flip-memory': '翻卡記憶', 'vocab-choice': '詞彙選擇', 'vocab-listening': '詞彙聽力', 'sentence-listening': '例句聽力', 'sentence-fill': '例句填空', 'sentence-reorder': '例句重組', 'sentence-speaking': '例句口說' }; return labels[mode] || mode; } // 生成測驗項目列表 const generateTestItems = (cards: ExtendedFlashcard[], userCEFR: string): TestItem[] => { const items: TestItem[] = []; let order = 1; cards.forEach(card => { const wordCEFR = card.difficultyLevel || 'A2'; const testTypes = getReviewTypesByCEFR(userCEFR, wordCEFR); testTypes.forEach(testType => { items.push({ id: `${card.id}-${testType}`, cardId: card.id, word: card.word, testType, testName: getModeLabel(testType), isCompleted: false, isCurrent: false, order }); order++; }); }); return items; } // 按詞卡分組測驗項目 const groupTestItemsByCard = (items: TestItem[]): CardTestGroup[] => { const grouped = items.reduce((acc, item) => { const cardId = item.cardId; if (!acc[cardId]) { const card = dueCards.find(c => c.id === cardId); const userCEFR = localStorage.getItem('userEnglishLevel') || 'A2'; const wordCEFR = card?.difficultyLevel || 'A2'; acc[cardId] = { cardId, word: item.word, context: getCurrentContext(userCEFR, wordCEFR), tests: [] }; } acc[cardId].tests.push(item); return acc; }, {} as Record); return Object.values(grouped); } // 初始化詞卡複習會話 const initializeCardReviewSession = (card: ExtendedFlashcard): CardReviewSession => { const userCEFR = localStorage.getItem('userEnglishLevel') || 'A2'; const wordCEFR = card.difficultyLevel || 'A2'; const plannedTests = getReviewTypesByCEFR(userCEFR, wordCEFR); return { cardId: card.id, word: card.word, plannedTests, completedTests: [], startedAt: new Date(), isCompleted: false }; } // 開始詞卡複習會話 const startCardReviewSession = (card: ExtendedFlashcard) => { const session = initializeCardReviewSession(card); setCurrentCardSession(session); // 更新會話映射 setCardReviewSessions(prev => new Map(prev.set(card.id, session))); console.log(`🎯 開始詞卡複習會話: ${card.word}`, { plannedTests: session.plannedTests, totalTests: session.plannedTests.length }); } // 檢查詞卡是否已完成所有測驗 const isCardReviewCompleted = (cardId: string): boolean => { const session = cardReviewSessions.get(cardId); return session?.isCompleted || false; } // 獲取詞卡的下一個測驗類型 const getNextTestTypeForCard = (cardId: string): string | null => { const session = cardReviewSessions.get(cardId); if (!session) return null; const completedTestTypes = session.completedTests.map(t => t.testType); const nextTestType = session.plannedTests.find(testType => !completedTestTypes.includes(testType) ); return nextTestType || null; } // 重置所有答題狀態 const resetAllStates = () => { setIsFlipped(false); setSelectedAnswer(null); setShowResult(false); setFillAnswer(''); setShowHint(false); setShuffledWords([]); setArrangedWords([]); setReorderResult(null); setQuizOptions([]); } // Quiz options generation useEffect(() => { if (!currentCard) return; const currentWord = currentCard.word; // Generate quiz options with current word and other words const otherWords = dueCards .filter(card => card.id !== currentCard.id) .map(card => card.word); // 優先從其他詞卡生成選項,必要時使用備用詞彙 const selectedOtherWords: string[] = []; // 從其他詞卡取得選項 for (const word of otherWords) { if (selectedOtherWords.length >= 3) break; if (word !== currentWord && !selectedOtherWords.includes(word)) { selectedOtherWords.push(word); } } // 如果詞卡不足,補充基礎詞彙 if (selectedOtherWords.length < 3) { const backupWords = ['important', 'beautiful', 'interesting', 'difficult', 'wonderful', 'excellent']; for (const word of backupWords) { if (selectedOtherWords.length >= 3) break; if (word !== currentWord && !selectedOtherWords.includes(word)) { selectedOtherWords.push(word); } } } // 確保有4個選項:當前詞彙 + 3個其他選項 const options = [currentWord, ...selectedOtherWords.slice(0, 3)].sort(() => Math.random() - 0.5); setQuizOptions(options); // Reset quiz state when card changes setSelectedAnswer(null); setShowResult(false); }, [currentCard, dueCards]) // Sentence options generation for sentence listening useEffect(() => { if (!currentCard || mode !== 'sentence-listening') return; const currentSentence = currentCard.example; // Generate sentence options with current sentence and other sentences const otherSentences = dueCards .filter(card => card.id !== currentCard.id) .map(card => card.example); // 優先從其他詞卡的例句生成選項 const selectedOtherSentences: string[] = []; // 從其他詞卡的例句取得選項 for (const sentence of otherSentences) { if (selectedOtherSentences.length >= 3) break; if (sentence !== currentSentence && !selectedOtherSentences.includes(sentence)) { selectedOtherSentences.push(sentence); } } // 如果例句不足,使用基礎例句補充 if (selectedOtherSentences.length < 3) { const backupSentences = [ 'This is a very important decision.', 'The weather looks beautiful today.', 'We need to find a good solution.', 'Learning English can be interesting.' ]; for (const sentence of backupSentences) { if (selectedOtherSentences.length >= 3) break; if (sentence !== currentSentence && !selectedOtherSentences.includes(sentence)) { selectedOtherSentences.push(sentence); } } } // 確保有4個選項:當前例句 + 3個其他選項 const options = [currentSentence, ...selectedOtherSentences.slice(0, 3)].sort(() => Math.random() - 0.5); setSentenceOptions(options); }, [currentCard, dueCards, mode]) // Initialize sentence reorder when card changes or mode switches to sentence-reorder useEffect(() => { if (mode === 'sentence-reorder' && currentCard) { const words = currentCard.example.split(/\s+/).filter(word => word.length > 0) const shuffled = [...words].sort(() => Math.random() - 0.5) setShuffledWords(shuffled) setArrangedWords([]) // 只在卡片或模式切換時重置結果,不在其他狀態變化時重置 if (reorderResult !== null) { setReorderResult(null) } } }, [currentCard, mode]) // 移除reorderResult依賴,避免循環重置 // Sentence reorder handlers const handleWordClick = (word: string) => { // Move word from shuffled to arranged setShuffledWords(prev => prev.filter(w => w !== word)) setArrangedWords(prev => [...prev, word]) setReorderResult(null) } const handleRemoveFromArranged = (word: string) => { setArrangedWords(prev => prev.filter(w => w !== word)) setShuffledWords(prev => [...prev, word]) setReorderResult(null) } const handleCheckReorderAnswer = async () => { if (!currentCard) return; const userSentence = arrangedWords.join(' ') const correctSentence = currentCard.example const isCorrect = userSentence.toLowerCase().trim() === correctSentence.toLowerCase().trim() setReorderResult(isCorrect) // 更新分數 setScore(prev => ({ correct: isCorrect ? prev.correct + 1 : prev.correct, total: prev.total + 1 })) // 記錄測驗結果 await recordTestResult(isCorrect, userSentence); } const handleResetReorder = () => { if (!currentCard) return; const words = currentCard.example.split(/\s+/).filter(word => word.length > 0) const shuffled = [...words].sort(() => Math.random() - 0.5) setShuffledWords(shuffled) setArrangedWords([]) setReorderResult(null) } const handleFlip = () => { setIsFlipped(!isFlipped) } // 移動到下一個測驗或下一張詞卡 const handleNext = async () => { if (!currentCard || !currentCardSession) return; // 檢查當前詞卡是否還有未完成的測驗 const nextTestType = getNextTestTypeForCard(currentCard.id); if (nextTestType) { // 當前詞卡還有測驗未完成,切換到下一個測驗類型 const modeMapping: { [key: string]: typeof mode } = { 'flip-memory': 'flip-memory', 'vocab-choice': 'vocab-choice', 'vocab-listening': 'vocab-listening', 'sentence-fill': 'sentence-fill', 'sentence-reorder': 'sentence-reorder', 'sentence-speaking': 'sentence-speaking', 'sentence-listening': 'sentence-listening' }; const nextMode = modeMapping[nextTestType] || 'flip-memory'; setMode(nextMode); resetAllStates(); // 更新測驗項目的當前狀態 setTestItems(prev => prev.map(item => item.cardId === currentCard.id && item.testType === nextTestType ? { ...item, isCurrent: true } : { ...item, isCurrent: false } ) ); console.log(`🔄 切換到下一個測驗: ${nextTestType} for ${currentCard.word}`); } else { // 當前詞卡的所有測驗都已完成,移動到下一張詞卡 if (currentCardIndex < dueCards.length - 1) { const nextCardIndex = currentCardIndex + 1; const nextCard = dueCards[nextCardIndex]; // 開始新詞卡的複習會話 startCardReviewSession(nextCard); await loadNextCardWithAutoMode(nextCardIndex); console.log(`➡️ 移動到下一張詞卡: ${nextCard.word}`); } else { // 所有詞卡都已完成 setShowComplete(true); console.log(`🎉 所有詞卡復習完成!`); } } } const handlePrevious = async () => { // 暫時保持簡單的向前導航 if (currentCardIndex > 0) { await loadNextCardWithAutoMode(currentCardIndex - 1); } } const handleQuizAnswer = async (answer: string) => { if (showResult || !currentCard) return setSelectedAnswer(answer) setShowResult(true) const isCorrect = answer === currentCard.word setScore(prev => ({ correct: isCorrect ? prev.correct + 1 : prev.correct, total: prev.total + 1 })) // 記錄測驗結果到資料庫 await recordTestResult(isCorrect, answer); } // 記錄測驗結果到資料庫(立即保存) const recordTestResult = async (isCorrect: boolean, userAnswer?: string, confidenceLevel?: number) => { if (!currentCard) return; // 檢查認證狀態 const token = localStorage.getItem('auth_token'); if (!token) { console.error('❌ 未找到認證token,請重新登入'); return; } try { console.log('🔄 開始記錄測驗結果到資料庫...', { flashcardId: currentCard.id, testType: mode, word: currentCard.word, isCorrect, hasToken: !!token }); // 立即記錄到資料庫 const result = await flashcardsService.recordTestCompletion({ flashcardId: currentCard.id, testType: mode, isCorrect, userAnswer, confidenceLevel, responseTimeMs: 2000 }); if (result.success) { console.log('✅ 測驗結果已記錄到資料庫:', mode, 'for', currentCard.word); // 更新本地UI狀態 setCompletedTests(prev => prev + 1); // 標記當前測驗項目為完成 setTestItems(prev => prev.map((item, index) => index === currentTestItemIndex ? { ...item, isCompleted: true, isCurrent: false } : item ) ); // 移到下一個測驗項目 setCurrentTestItemIndex(prev => prev + 1); // 檢查是否還有剩餘測驗 setTimeout(() => { loadNextUncompletedTest(); }, 1500); } else { console.error('❌ 記錄測驗結果失敗:', result.error); if (result.error?.includes('Test already completed') || result.error?.includes('already completed')) { console.log('⚠️ 測驗已完成,跳到下一個'); loadNextUncompletedTest(); } else { // 其他錯誤,先更新UI狀態避免卡住 setCompletedTests(prev => prev + 1); setCurrentTestItemIndex(prev => prev + 1); setTimeout(() => { loadNextUncompletedTest(); }, 1500); } } } catch (error) { console.error('💥 記錄測驗結果異常:', error); // 即使出錯也更新進度,避免卡住 setCompletedTests(prev => prev + 1); setCurrentTestItemIndex(prev => prev + 1); setTimeout(() => { loadNextUncompletedTest(); }, 1500); } } // 載入下一個未完成的測驗 const loadNextUncompletedTest = () => { if (currentTestItemIndex + 1 < testItems.length) { // 還有測驗項目 const nextTestItem = testItems[currentTestItemIndex + 1]; const nextCard = dueCards.find(c => c.id === nextTestItem.cardId); if (nextCard) { setCurrentCard(nextCard); setCurrentCardIndex(dueCards.findIndex(c => c.id === nextCard.id)); // 設置下一個測驗類型 const modeMapping: { [key: string]: typeof mode } = { 'flip-memory': 'flip-memory', 'vocab-choice': 'vocab-choice', 'vocab-listening': 'vocab-listening', 'sentence-fill': 'sentence-fill', 'sentence-reorder': 'sentence-reorder', 'sentence-speaking': 'sentence-speaking', 'sentence-listening': 'sentence-listening' }; const nextMode = modeMapping[nextTestItem.testType] || 'flip-memory'; setMode(nextMode); resetAllStates(); // 更新測驗項目的當前狀態 setTestItems(prev => prev.map((item, index) => index === currentTestItemIndex + 1 ? { ...item, isCurrent: true } : { ...item, isCurrent: false } ) ); console.log(`🔄 載入下一個測驗: ${nextCard.word} - ${nextTestItem.testType}`); } } else { // 所有測驗完成 console.log('🎉 所有測驗完成!'); setShowComplete(true); } } // 完成詞卡複習並提交到後端 const completeCardReview = async (session: CardReviewSession) => { try { // 計算綜合表現指標 const correctCount = session.completedTests.filter(t => t.isCorrect).length; const totalTests = session.completedTests.length; const accuracy = totalTests > 0 ? correctCount / totalTests : 0; // 計算平均信心等級(用於翻卡記憶測驗) const confidenceTests = session.completedTests.filter(t => t.confidenceLevel !== undefined); const avgConfidence = confidenceTests.length > 0 ? confidenceTests.reduce((sum, t) => sum + (t.confidenceLevel || 3), 0) / confidenceTests.length : 3; // 計算平均答題時間 const avgResponseTime = session.completedTests.reduce((sum, t) => sum + t.responseTimeMs, 0) / totalTests; // 確定主要測驗類型(用於後端SM2算法) const primaryTestType = session.completedTests[0]?.testType || 'flip-memory'; console.log(`🔥 提交詞卡完整復習結果:`, { word: session.word, accuracy: `${Math.round(accuracy * 100)}%`, avgConfidence, avgResponseTime: `${avgResponseTime}ms`, primaryTestType, completedTests: session.completedTests.length }); // 提交到後端 const result = await flashcardsService.submitReview(session.cardId, { isCorrect: accuracy >= 0.7, // 70%以上正確率視為通過 confidenceLevel: Math.round(avgConfidence), questionType: primaryTestType, userAnswer: `綜合${totalTests}個測驗,正確率${Math.round(accuracy * 100)}%`, timeTaken: Math.round(avgResponseTime) }); if (result.success && result.data) { console.log('✅ 詞卡復習結果提交成功:', result.data); // 更新詞卡的熟悉度等資訊 if (currentCard && currentCard.id === session.cardId) { setCurrentCard(prev => prev ? { ...prev, masteryLevel: result.data!.masteryLevel, nextReviewDate: result.data!.nextReviewDate } : null); } // 增加已完成詞卡數量 setCompletedCards(prev => prev + 1); console.log(`🎉 詞卡 ${session.word} 復習完成!新熟悉度: ${result.data.masteryLevel}%, 下次復習: ${result.data.nextReviewDate}`); } else { console.error('詞卡復習結果提交失敗:', result.error); } } catch (error) { console.error('完成詞卡復習時發生錯誤:', error); } } const handleFillAnswer = async () => { if (showResult || !currentCard) return setShowResult(true) const isCorrect = fillAnswer.toLowerCase().trim() === currentCard.word.toLowerCase() setScore(prev => ({ correct: isCorrect ? prev.correct + 1 : prev.correct, total: prev.total + 1 })) // 記錄測驗結果 await recordTestResult(isCorrect, fillAnswer); } const handleListeningAnswer = async (answer: string) => { if (showResult || !currentCard) return setSelectedAnswer(answer) setShowResult(true) const isCorrect = answer === currentCard.word setScore(prev => ({ correct: isCorrect ? prev.correct + 1 : prev.correct, total: prev.total + 1 })) // 記錄測驗結果 await recordTestResult(isCorrect, answer); } const handleSpeakingAnswer = async (transcript: string) => { if (!currentCard) return setShowResult(true) const isCorrect = transcript.toLowerCase().includes(currentCard.word.toLowerCase()) setScore(prev => ({ correct: isCorrect ? prev.correct + 1 : prev.correct, total: prev.total + 1 })) // 記錄測驗結果 await recordTestResult(isCorrect, transcript); } const handleSentenceListeningAnswer = async (answer: string) => { if (showResult || !currentCard) return setSelectedAnswer(answer) setShowResult(true) const isCorrect = answer === currentCard.example setScore(prev => ({ correct: isCorrect ? prev.correct + 1 : prev.correct, total: prev.total + 1 })) // 記錄測驗結果 await recordTestResult(isCorrect, answer); } const handleReportSubmit = () => { console.log('Report submitted:', { card: reportingCard, reason: reportReason }) setShowReportModal(false) setReportReason('') setReportingCard(null) } const handleRestart = async () => { setScore({ correct: 0, total: 0 }) setCompletedTests(0) setTotalTests(0) setTestItems([]) setCurrentTestItemIndex(0) setShowComplete(false) setShowNoDueCards(false) await loadDueCards(); // 重新載入到期詞卡 } // Show loading screen until mounted or while loading cards if (!mounted || isLoadingCard) { return (
{isAutoSelecting ? '系統正在選擇最適合的複習方式...' : '載入中...'}
) } // Show no due cards screen if (showNoDueCards) { return (
📚

今日學習已完成!

目前沒有到期需要複習的詞卡。您可以:

💡 建議行動
  • • 前往詞卡管理頁面新增詞卡
  • • 查看學習統計和進度
  • • 調整學習目標和設定
) } // Show current card interface if (!currentCard) { return (
載入詞卡中...
) } return (
{/* Navigation */}
{/* Progress Bar */}
學習進度
{/* 詞卡進度 */}
詞卡: {completedCards} / {dueCards.length} {dueCards.length > 0 && ( ({Math.round((completedCards / dueCards.length) * 100)}%) )}
{/* 測驗進度 */}
{/* 雙層進度條 */}
{/* 詞卡進度條 */}
詞卡
0 ? (completedCards / dueCards.length) * 100 : 0}%` }} >
{dueCards.length > 0 ? Math.round((completedCards / dueCards.length) * 100) : 0}%
{/* 測驗進度條 */}
測驗
setShowTaskListModal(true)} title="點擊查看詳細任務清單" >
0 ? (completedTests / totalTests) * 100 : 0}%` }} >
{totalTests > 0 ? Math.round((completedTests / totalTests) * 100) : 0}%
{mode === 'flip-memory' ? ( /* Flip Card Mode */
{/* Error Report Button for Flip Mode */}
{/* Front */}
{/* Title and Instructions */}

翻卡記憶

{currentCard.difficultyLevel}
{/* Instructions Test Action */}

點擊卡片翻面,根據你對單字的熟悉程度進行自我評估:

{/* Word Display */}

{currentCard.word}

{currentCard.pronunciation}
{/* Back */}
{/* Content Sections */}
{/* Definition */}

定義

{currentCard.definition}

{/* Example */}

例句

"{currentCard.example}"

"{currentCard.exampleTranslation}"

{/* Synonyms */}

同義詞

{(currentCard.synonyms || []).map((synonym, index) => ( {synonym} ))}
{/* Navigation */}
) : mode === 'vocab-choice' ? ( /* Vocab Choice Mode - 詞彙選擇 */
{/* Error Report Button for Quiz Mode */}
{/* Title in top-left */}

詞彙選擇

{currentCard.difficultyLevel}
{/* Instructions Test Action */}

請選擇符合上述定義的英文詞彙:

定義

{currentCard.definition}

{quizOptions.map((option, idx) => ( ))}
{showResult && (

{selectedAnswer === currentCard.word ? '正確!' : '錯誤!'}

{selectedAnswer !== currentCard.word && (

正確答案是:{currentCard.word}

)}
發音: {currentCard.pronunciation}
)}
{/* Navigation */}
) : mode === 'sentence-fill' ? ( /* Fill in the Blank Mode - 填空題 */
{/* Error Report Button for Fill Mode */}
{/* Title in top-left */}

例句填空

{currentCard.difficultyLevel}
{/* Example Image */} {currentCard.exampleImage && (
Example illustration setModalImage(currentCard.exampleImage || null)} />
)} {/* Instructions Test Action */}

請點擊例句中的空白處輸入正確的單字:

{/* Example Sentence with Blanks */}
{currentCard.example.split(new RegExp(`(${currentCard.word})`, 'gi')).map((part, index) => { const isTargetWord = part.toLowerCase() === currentCard.word.toLowerCase(); return isTargetWord ? ( setFillAnswer(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter' && !showResult && fillAnswer.trim()) { handleFillAnswer() } }} placeholder="" disabled={showResult} className={`inline-block px-2 py-1 text-center bg-transparent focus:outline-none disabled:bg-gray-100 ${ fillAnswer ? 'border-b-2 border-blue-500' : 'border-b-2 border-dashed border-gray-400 focus:border-blue-400 focus:border-solid' }`} style={{ width: `${Math.max(100, Math.max(currentCard.word.length * 12, fillAnswer.length * 12 + 20))}px` }} /> {!fillAnswer && ( ____ )} ) : ( {part} ); })}
{/* Action Buttons */}
{!showResult && fillAnswer.trim() && ( )}
{/* Hint Section */} {showHint && (

詞彙定義:

{currentCard.definition}

)} {showResult && (

{fillAnswer.toLowerCase().trim() === currentCard.word.toLowerCase() ? '正確!' : '錯誤!'}

{fillAnswer.toLowerCase().trim() !== currentCard.word.toLowerCase() && (

正確答案是:{currentCard.word}

)}

{currentCard.pronunciation}

)}
{/* Navigation */}
) : mode === 'vocab-listening' ? ( /* Listening Test Mode - 聽力測試 */
{/* Error Report Button for Listening Mode */}
{/* Title in top-left */}

詞彙聽力 (暫時不上線)

{currentCard.difficultyLevel}
{/* Instructions Test Action */}

請聽發音並選擇正確的英文單字:

{/* Content Sections */}
{/* Audio */}

發音

{currentCard.pronunciation}
{/* Word Options */}
{[currentCard.word, 'determine', 'achieve', 'consider'].map((word) => ( ))}
{showResult && (

{selectedAnswer === currentCard.word ? '正確!' : '錯誤!'}

{selectedAnswer !== currentCard.word && (

正確答案是:{currentCard.word}

發音:{currentCard.pronunciation}
)}
)}
{/* Navigation */}
) : mode === 'sentence-speaking' ? ( /* Speaking Test Mode - 口說測試 */
{/* Error Report Button for Speaking Mode */}
{/* Title in top-left */}

例句口說

{currentCard.difficultyLevel}
{ // 簡化處理:直接顯示結果 handleSpeakingAnswer(currentCard.example) }} />
{showResult && (

錄音完成!

系統正在評估你的發音...

)}
{/* Navigation */}
) : mode === 'sentence-listening' ? ( /* Sentence Listening Test Mode - 例句聽力題 */
{/* Error Report Button */}
{/* Title in top-left */}

例句聽力

{currentCard.difficultyLevel}
{/* Instructions Test Action */}

請聽例句並選擇正確的選項:

點擊播放聽例句

{sentenceOptions.map((sentence, idx) => ( ))}
{showResult && (

{selectedAnswer === currentCard.example ? '正確!' : '錯誤!'}

{selectedAnswer !== currentCard.example && (

正確答案是:"{currentCard.example}"

中文翻譯:{currentCard.exampleTranslation}

)}
)}
{/* Navigation */}
) : mode === 'sentence-reorder' ? ( /* Sentence Reorder Mode - 例句重組題 */
{/* Error Report Button */}
{/* Title in top-left */}

例句重組

{currentCard.difficultyLevel}
{/* Example Image */} {currentCard.exampleImage && (
Example illustration setModalImage(currentCard.exampleImage || null)} />
)} {/* Arranged Sentence Area */}

重組區域:

{arrangedWords.length === 0 ? (
答案區
) : (
{arrangedWords.map((word, index) => (
handleRemoveFromArranged(word)} > {word} ×
))}
)}
{/* Instructions Test Action */}

點擊下方單字,依序重組成正確的句子:

{/* Shuffled Words */}

可用單字:

{shuffledWords.length === 0 ? (
所有單字都已使用
) : (
{shuffledWords.map((word, index) => ( ))}
)}
{/* Control Buttons */}
{arrangedWords.length > 0 && ( )}
{/* Result Feedback */} {reorderResult !== null && (

{reorderResult ? '正確!' : '錯誤!'}

{!reorderResult && (

正確答案是:"{currentCard.example}"

)}

{currentCard.exampleTranslation}

)}
{/* Navigation */}
) : null} {/* Report Modal */} {showReportModal && (

回報錯誤

單字:{reportingCard?.word}

)} {/* Image Modal */} {modalImage && (
setModalImage(null)} >
放大圖片
)} {/* Task List Modal */} {showTaskListModal && (
{/* Header */}

📚 學習任務清單

{/* Content */}
{/* 進度統計 */}
測驗進度: {completedTests} / {totalTests} ({totalTests > 0 ? Math.round((completedTests / totalTests) * 100) : 0}%)
✅ 已完成: {testItems.filter(item => item.isCompleted).length} ⏳ 進行中: {testItems.filter(item => item.isCurrent).length} ⚪ 待完成: {testItems.filter(item => !item.isCompleted && !item.isCurrent).length}
0 ? (completedTests / totalTests) * 100 : 0}%` }} >
{/* 任務清單 */}
{groupTestItemsByCard(testItems).map((cardGroup, cardIndex) => (
{/* 詞卡標題 */}
詞卡{cardIndex + 1}: {cardGroup.word} {cardGroup.context} {cardGroup.tests.length}個測驗
{/* 測驗項目 */}
{cardGroup.tests.map(test => (
{/* 狀態圖標 */} {test.isCompleted ? '✅' : test.isCurrent ? '⏳' : '⚪'} {/* 測驗資訊 */}
{test.order}. {test.testName}
{test.isCompleted ? '已完成' : test.isCurrent ? '進行中' : '待完成'}
))}
))}
{testItems.length === 0 && (
📚

還沒有生成任務清單

)}
)} {/* Complete Modal */} {showComplete && ( router.push('/dashboard')} /> )} {/* No Due Cards Modal */} {showNoDueCards && (
📚

今日學習已完成!

目前沒有到期需要複習的詞卡。您可以:

💡 建議行動
  • • 前往詞卡管理頁面新增詞卡
  • • 查看學習統計和進度
  • • 調整學習目標和設定
)}
) }