dramaling-vocab-learning/frontend/hooks/review/useTestQueue.ts

254 lines
7.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useState } from 'react'
import { flashcardsService } from '@/lib/services/flashcards'
import { getReviewTypesByCEFR, getModeLabel } from '@/lib/utils/cefrUtils'
// 測驗項目接口
interface TestItem {
id: string
cardId: string
word: string
testType: string
testName: string
isCompleted: boolean
isCurrent: boolean
order: number
}
// 測驗結果接口
interface TestResult {
testType: string
isCorrect: boolean
userAnswer?: string
confidenceLevel?: number
responseTimeMs: number
completedAt: Date
}
// Hook狀態接口
interface TestQueueState {
totalTests: number
completedTests: number
testItems: TestItem[]
currentTestItemIndex: number
}
// Hook返回接口
interface UseTestQueueReturn extends TestQueueState {
initializeTestQueue: (cards: any[], completedTests: any[]) => void
recordTestResult: (isCorrect: boolean, userAnswer?: string, confidenceLevel?: number) => Promise<void>
loadNextUncompletedTest: () => void
skipCurrentTest: () => void
resetTestQueue: () => void
getCompletedTestsForCards: (cardIds: string[]) => Promise<any[]>
}
export const useTestQueue = (): UseTestQueueReturn => {
// 測驗隊列狀態
const [totalTests, setTotalTests] = useState(0)
const [completedTests, setCompletedTests] = useState(0)
const [testItems, setTestItems] = useState<TestItem[]>([])
const [currentTestItemIndex, setCurrentTestItemIndex] = useState(0)
// 初始化測驗隊列
const initializeTestQueue = (cards: any[], completedTests: any[] = []): void => {
const userCEFRLevel = localStorage.getItem('userEnglishLevel') || 'A2'
let remainingTestItems: TestItem[] = []
let order = 1
cards.forEach(card => {
const wordCEFRLevel = card.difficultyLevel || 'A2'
const allTestTypes = getReviewTypesByCEFR(userCEFRLevel, wordCEFRLevel)
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('🎉 所有測驗都已完成!')
return
}
console.log('📝 剩餘測驗項目:', remainingTestItems.length, '個')
setTotalTests(remainingTestItems.length)
setTestItems(remainingTestItems)
setCurrentTestItemIndex(0)
setCompletedTests(0)
// 標記第一個測驗為當前
setTestItems(prev =>
prev.map((item, index) =>
index === 0 ? { ...item, isCurrent: true } : item
)
)
}
// 獲取已完成的測驗
const getCompletedTestsForCards = async (cardIds: string[]): Promise<any[]> => {
try {
const result = await flashcardsService.getCompletedTests(cardIds)
if (result.success && result.data) {
console.log('📊 已完成測驗:', result.data.length, '個')
return result.data
}
} catch (error) {
console.error('💥 查詢已完成測驗異常:', error)
}
return []
}
// 記錄測驗結果
const recordTestResult = async (
isCorrect: boolean,
userAnswer?: string,
confidenceLevel?: number
): Promise<void> => {
const token = localStorage.getItem('auth_token')
if (!token) {
console.error('❌ 未找到認證token請重新登入')
return
}
const currentTestItem = testItems[currentTestItemIndex]
if (!currentTestItem) return
try {
console.log('🔄 開始記錄測驗結果到資料庫...', {
flashcardId: currentTestItem.cardId,
testType: currentTestItem.testType,
word: currentTestItem.word,
isCorrect,
hasToken: !!token
})
const result = await flashcardsService.recordTestCompletion({
flashcardId: currentTestItem.cardId,
testType: currentTestItem.testType,
isCorrect,
userAnswer,
confidenceLevel,
responseTimeMs: 2000
})
if (result.success) {
console.log('✅ 測驗結果已記錄到資料庫:', currentTestItem.testType, 'for', currentTestItem.word)
// 更新本地狀態
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)
handleTestError()
}
} catch (error) {
console.error('💥 記錄測驗結果異常:', error)
handleTestError()
}
}
// 處理測驗錯誤
const handleTestError = (): void => {
setCompletedTests(prev => prev + 1)
setCurrentTestItemIndex(prev => prev + 1)
setTimeout(() => {
loadNextUncompletedTest()
}, 1500)
}
// 載入下一個未完成測驗
const loadNextUncompletedTest = (): void => {
if (currentTestItemIndex + 1 < testItems.length) {
const nextIndex = currentTestItemIndex + 1
setTestItems(prev =>
prev.map((item, index) =>
index === nextIndex
? { ...item, isCurrent: true }
: { ...item, isCurrent: false }
)
)
console.log(`🔄 載入下一個測驗: ${testItems[nextIndex]?.word} - ${testItems[nextIndex]?.testType}`)
} else {
console.log('🎉 所有測驗完成!')
}
}
// 跳過當前測驗
const skipCurrentTest = (): void => {
// 將當前測驗移到隊列最後
const currentTest = testItems[currentTestItemIndex]
if (!currentTest) return
setTestItems(prev => {
const newItems = [...prev]
// 移除當前項目
newItems.splice(currentTestItemIndex, 1)
// 添加到最後
newItems.push({ ...currentTest, isCurrent: false })
// 標記新的當前項目
if (newItems[currentTestItemIndex]) {
newItems[currentTestItemIndex].isCurrent = true
}
return newItems
})
console.log(`⏭️ 跳過測驗: ${currentTest.word} - ${currentTest.testType}`)
}
// 重置測驗隊列
const resetTestQueue = (): void => {
setTotalTests(0)
setCompletedTests(0)
setTestItems([])
setCurrentTestItemIndex(0)
}
return {
// 狀態
totalTests,
completedTests,
testItems,
currentTestItemIndex,
// 操作函數
initializeTestQueue,
recordTestResult,
loadNextUncompletedTest,
skipCurrentTest,
resetTestQueue,
getCompletedTestsForCards
}
}