221 lines
6.3 KiB
TypeScript
221 lines
6.3 KiB
TypeScript
import { useReducer, useEffect, useMemo } from 'react'
|
|
import {
|
|
INITIAL_TEST_ITEMS,
|
|
QuizItem,
|
|
sortQuizItemsByPriority,
|
|
generateVocabOptions,
|
|
SIMPLE_CARDS
|
|
} from '@/lib/data/reviewSimpleData'
|
|
|
|
interface ReviewState {
|
|
quizItems: QuizItem[]
|
|
score: { correct: number; total: number }
|
|
isComplete: boolean
|
|
}
|
|
|
|
type ReviewAction =
|
|
| { type: 'LOAD_PROGRESS'; payload: ReviewState }
|
|
| { type: 'ANSWER_TEST_ITEM'; payload: { quizItemId: string; confidence: number } }
|
|
| { type: 'SKIP_TEST_ITEM'; payload: { quizItemId: string } }
|
|
| { type: 'RESTART' }
|
|
|
|
// 內部測驗項目更新函數
|
|
const updateQuizItem = (
|
|
quizItems: QuizItem[],
|
|
quizItemId: string,
|
|
updates: Partial<QuizItem>
|
|
): QuizItem[] => {
|
|
return quizItems.map((item) =>
|
|
item.id === quizItemId
|
|
? { ...item, ...updates }
|
|
: item
|
|
)
|
|
}
|
|
|
|
const reviewReducer = (state: ReviewState, action: ReviewAction): ReviewState => {
|
|
switch (action.type) {
|
|
case 'LOAD_PROGRESS':
|
|
return action.payload
|
|
|
|
case 'ANSWER_TEST_ITEM': {
|
|
const { quizItemId, confidence } = action.payload
|
|
const isCorrect = confidence >= 1 // 修正:一般(1分)以上都算答對
|
|
|
|
const quizItem = state.quizItems.find(item => item.id === quizItemId)
|
|
if (!quizItem) return state
|
|
|
|
// 修正:只有答對才標記為完成,答錯只增加錯誤次數
|
|
const updatedQuizItems = updateQuizItem(state.quizItems, quizItemId,
|
|
isCorrect
|
|
? { isCompleted: true } // 答對:標記完成
|
|
: { wrongCount: quizItem.wrongCount + 1 } // 答錯:只增加錯誤次數,不完成
|
|
)
|
|
|
|
const newScore = {
|
|
correct: state.score.correct + (isCorrect ? 1 : 0),
|
|
total: state.score.total + 1
|
|
}
|
|
|
|
const remainingQuizItems = updatedQuizItems.filter(item => !item.isCompleted)
|
|
const isComplete = remainingQuizItems.length === 0
|
|
|
|
return {
|
|
quizItems: updatedQuizItems,
|
|
score: newScore,
|
|
isComplete
|
|
}
|
|
}
|
|
|
|
case 'SKIP_TEST_ITEM': {
|
|
const { quizItemId } = action.payload
|
|
|
|
const quizItem = state.quizItems.find(item => item.id === quizItemId)
|
|
if (!quizItem) return state
|
|
|
|
const updatedQuizItems = updateQuizItem(state.quizItems, quizItemId, {
|
|
skipCount: quizItem.skipCount + 1
|
|
})
|
|
|
|
const remainingQuizItems = updatedQuizItems.filter(item => !item.isCompleted)
|
|
const isComplete = remainingQuizItems.length === 0
|
|
|
|
return {
|
|
quizItems: updatedQuizItems,
|
|
score: state.score,
|
|
isComplete
|
|
}
|
|
}
|
|
|
|
case 'RESTART':
|
|
return {
|
|
quizItems: INITIAL_TEST_ITEMS,
|
|
score: { correct: 0, total: 0 },
|
|
isComplete: false
|
|
}
|
|
|
|
default:
|
|
return state
|
|
}
|
|
}
|
|
|
|
export function useReviewSession() {
|
|
// 使用 useReducer 統一狀態管理
|
|
const [state, dispatch] = useReducer(reviewReducer, {
|
|
quizItems: INITIAL_TEST_ITEMS,
|
|
score: { correct: 0, total: 0 },
|
|
isComplete: false
|
|
})
|
|
|
|
const { quizItems, score, isComplete } = state
|
|
|
|
// 智能排序獲取當前測驗項目 - 使用 useMemo 優化性能
|
|
const sortedQuizItems = useMemo(() => sortQuizItemsByPriority(quizItems), [quizItems])
|
|
const incompleteQuizItems = useMemo(() =>
|
|
sortedQuizItems.filter((item: QuizItem) => !item.isCompleted),
|
|
[sortedQuizItems]
|
|
)
|
|
const currentQuizItem = incompleteQuizItems[0] // 總是選擇優先級最高的未完成測驗項目
|
|
const currentCard = currentQuizItem?.cardData // 當前詞卡數據
|
|
|
|
// localStorage進度保存和載入
|
|
useEffect(() => {
|
|
// 載入保存的進度
|
|
const savedProgress = localStorage.getItem('review-linear-progress')
|
|
if (savedProgress) {
|
|
try {
|
|
const parsed = JSON.parse(savedProgress)
|
|
const saveTime = new Date(parsed.timestamp)
|
|
const now = new Date()
|
|
const isToday = saveTime.toDateString() === now.toDateString()
|
|
|
|
if (isToday && parsed.quizItems) {
|
|
dispatch({
|
|
type: 'LOAD_PROGRESS',
|
|
payload: {
|
|
quizItems: parsed.quizItems,
|
|
score: parsed.score || { correct: 0, total: 0 },
|
|
isComplete: parsed.isComplete || false
|
|
}
|
|
})
|
|
console.log('📖 載入保存的線性複習進度')
|
|
}
|
|
} catch (error) {
|
|
console.warn('進度載入失敗:', error)
|
|
localStorage.removeItem('review-linear-progress')
|
|
}
|
|
}
|
|
}, [])
|
|
|
|
// 保存進度到localStorage
|
|
const saveProgress = () => {
|
|
const progress = {
|
|
quizItems,
|
|
score,
|
|
isComplete,
|
|
timestamp: new Date().toISOString()
|
|
}
|
|
localStorage.setItem('review-linear-progress', JSON.stringify(progress))
|
|
console.log('💾 線性進度已保存')
|
|
}
|
|
|
|
// 處理測驗項目答題
|
|
const handleAnswer = (confidence: number) => {
|
|
if (!currentQuizItem) return
|
|
|
|
dispatch({
|
|
type: 'ANSWER_TEST_ITEM',
|
|
payload: { quizItemId: currentQuizItem.id, confidence }
|
|
})
|
|
|
|
// 保存進度
|
|
setTimeout(() => saveProgress(), 100)
|
|
}
|
|
|
|
// 處理測驗項目跳過
|
|
const handleSkip = () => {
|
|
if (!currentQuizItem) return
|
|
|
|
dispatch({
|
|
type: 'SKIP_TEST_ITEM',
|
|
payload: { quizItemId: currentQuizItem.id }
|
|
})
|
|
|
|
// 保存進度
|
|
setTimeout(() => saveProgress(), 100)
|
|
}
|
|
|
|
// 重新開始 - 重置所有狀態
|
|
const handleRestart = () => {
|
|
dispatch({ type: 'RESTART' })
|
|
localStorage.removeItem('review-linear-progress')
|
|
console.log('🔄 線性複習進度已重置')
|
|
}
|
|
|
|
// 生成詞彙選擇選項 (僅當當前是詞彙選擇測驗時)
|
|
const vocabOptions = useMemo(() => {
|
|
if (currentQuizItem?.quizType === 'vocab-choice' && currentCard) {
|
|
return generateVocabOptions(currentCard.word, SIMPLE_CARDS)
|
|
}
|
|
return []
|
|
}, [currentQuizItem, currentCard])
|
|
|
|
return {
|
|
// 狀態
|
|
quizItems,
|
|
score,
|
|
isComplete,
|
|
currentQuizItem,
|
|
currentCard,
|
|
vocabOptions,
|
|
sortedQuizItems,
|
|
|
|
// 計算屬性
|
|
totalQuizItems: quizItems.length,
|
|
completedQuizItems: quizItems.filter(item => item.isCompleted).length,
|
|
|
|
// 動作
|
|
handleAnswer,
|
|
handleSkip,
|
|
handleRestart
|
|
}
|
|
} |