254 lines
7.3 KiB
TypeScript
254 lines
7.3 KiB
TypeScript
import { useState } from 'react'
|
||
import { flashcardsService } from '@/lib/services/flashcards'
|
||
import { getReviewTypesByCEFR } 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: 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
|
||
}
|
||
} |