diff --git a/frontend/app/review-simple/data.ts b/frontend/app/review-simple/data.ts index a262b17..6014212 100644 --- a/frontend/app/review-simple/data.ts +++ b/frontend/app/review-simple/data.ts @@ -29,10 +29,6 @@ export interface CardState extends ApiFlashcard { wrongCount: number // 答錯次數 isCompleted: boolean // 是否已完成 originalOrder: number // 原始順序 - - // 計算屬性 - delayScore: number // 延遲分數 = skipCount + wrongCount - lastAttemptAt: Date // 最後嘗試時間 } export interface ApiResponse { @@ -54,9 +50,7 @@ const addStateFields = (flashcard: ApiFlashcard, index: number): CardState => ({ skipCount: 0, wrongCount: 0, isCompleted: false, - originalOrder: index, - delayScore: 0, - lastAttemptAt: new Date() + originalOrder: index }) // 提取詞卡數據 (方便組件使用) @@ -81,29 +75,3 @@ export const sortCardsByPriority = (cards: CardState[]): CardState[] => { return a.originalOrder - b.originalOrder }) } - -export const updateCardState = ( - cards: CardState[], - currentIndex: number, - updates: Partial -): CardState[] => { - return cards.map((card, index) => - index === currentIndex - ? { - ...card, - ...updates, - delayScore: (updates.skipCount ?? card.skipCount) + (updates.wrongCount ?? card.wrongCount), - lastAttemptAt: new Date() - } - : card - ) -} - -// 模擬API調用函數 (為未來API集成做準備) -export const mockApiCall = async (): Promise => { - // 模擬網路延遲 - await new Promise(resolve => setTimeout(resolve, 500)) - - // 返回模擬數據 - return MOCK_API_RESPONSE -} \ No newline at end of file diff --git a/frontend/app/review-simple/hooks/useReviewSession.ts b/frontend/app/review-simple/hooks/useReviewSession.ts new file mode 100644 index 0000000..d9898e2 --- /dev/null +++ b/frontend/app/review-simple/hooks/useReviewSession.ts @@ -0,0 +1,210 @@ +import { useReducer, useEffect, useMemo } from 'react' +import { SIMPLE_CARDS, CardState, sortCardsByPriority } from '../data' + +interface ReviewState { + cards: CardState[] + score: { correct: number; total: number } + isComplete: boolean +} + +type ReviewAction = + | { type: 'LOAD_PROGRESS'; payload: ReviewState } + | { type: 'ANSWER_CARD'; payload: { cardId: string; confidence: number } } + | { type: 'SKIP_CARD'; payload: { cardId: string } } + | { type: 'RESTART' } + +// 內部狀態更新函數 +const updateCardState = ( + cards: CardState[], + cardIndex: number, + updates: Partial +): CardState[] => { + return cards.map((card, index) => + index === cardIndex + ? { ...card, ...updates } + : card + ) +} + +const reviewReducer = (state: ReviewState, action: ReviewAction): ReviewState => { + switch (action.type) { + case 'LOAD_PROGRESS': + return action.payload + + case 'ANSWER_CARD': { + const { cardId, confidence } = action.payload + const isCorrect = confidence >= 2 + + // 使用 Map 優化查找性能 + const cardMap = new Map(state.cards.map((card, index) => [card.id, { card, index }])) + const cardData = cardMap.get(cardId) + + if (!cardData) return state + + const { index: cardIndex } = cardData + const currentCard = state.cards[cardIndex] + + const updatedCards = updateCardState(state.cards, cardIndex, { + isCompleted: isCorrect, + wrongCount: isCorrect ? currentCard.wrongCount : currentCard.wrongCount + 1 + }) + + const newScore = { + correct: state.score.correct + (isCorrect ? 1 : 0), + total: state.score.total + 1 + } + + const remainingCards = updatedCards.filter(card => !card.isCompleted) + const isComplete = remainingCards.length === 0 + + return { + cards: updatedCards, + score: newScore, + isComplete + } + } + + case 'SKIP_CARD': { + const { cardId } = action.payload + + // 使用 Map 優化查找性能 + const cardMap = new Map(state.cards.map((card, index) => [card.id, { card, index }])) + const cardData = cardMap.get(cardId) + + if (!cardData) return state + + const { index: cardIndex } = cardData + const currentCard = state.cards[cardIndex] + + const updatedCards = updateCardState(state.cards, cardIndex, { + skipCount: currentCard.skipCount + 1 + }) + + const remainingCards = updatedCards.filter(card => !card.isCompleted) + const isComplete = remainingCards.length === 0 + + return { + cards: updatedCards, + score: state.score, + isComplete + } + } + + case 'RESTART': + return { + cards: SIMPLE_CARDS, + score: { correct: 0, total: 0 }, + isComplete: false + } + + default: + return state + } +} + +export function useReviewSession() { + // 使用 useReducer 統一狀態管理 + const [state, dispatch] = useReducer(reviewReducer, { + cards: SIMPLE_CARDS, + score: { correct: 0, total: 0 }, + isComplete: false + }) + + const { cards, score, isComplete } = state + + // 智能排序獲取當前卡片 - 使用 useMemo 優化性能 + const sortedCards = useMemo(() => sortCardsByPriority(cards), [cards]) + const incompleteCards = useMemo(() => + sortedCards.filter((card: CardState) => !card.isCompleted), + [sortedCards] + ) + const currentCard = incompleteCards[0] // 總是選擇優先級最高的未完成卡片 + + // localStorage進度保存和載入 + useEffect(() => { + // 載入保存的進度 + const savedProgress = localStorage.getItem('review-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.cards) { + dispatch({ + type: 'LOAD_PROGRESS', + payload: { + cards: parsed.cards, + score: parsed.score || { correct: 0, total: 0 }, + isComplete: parsed.isComplete || false + } + }) + console.log('📖 載入保存的複習進度') + } + } catch (error) { + console.warn('進度載入失敗:', error) + localStorage.removeItem('review-progress') + } + } + }, []) + + // 保存進度到localStorage + const saveProgress = () => { + const progress = { + cards, + score, + isComplete, + timestamp: new Date().toISOString() + } + localStorage.setItem('review-progress', JSON.stringify(progress)) + console.log('💾 進度已保存') + } + + // 處理答題 - 使用 dispatch 統一管理 + const handleAnswer = (confidence: number) => { + if (!currentCard) return + + dispatch({ + type: 'ANSWER_CARD', + payload: { cardId: currentCard.id, confidence } + }) + + // 保存進度 + setTimeout(() => saveProgress(), 100) // 延遲一點確保狀態更新 + } + + // 處理跳過 - 使用 dispatch 統一管理 + const handleSkip = () => { + if (!currentCard) return + + dispatch({ + type: 'SKIP_CARD', + payload: { cardId: currentCard.id } + }) + + // 保存進度 + setTimeout(() => saveProgress(), 100) // 延遲一點確保狀態更新 + } + + // 重新開始 - 重置所有狀態 + const handleRestart = () => { + dispatch({ type: 'RESTART' }) + localStorage.removeItem('review-progress') // 清除保存的進度 + console.log('🔄 複習進度已重置') + } + + return { + // 狀態 + cards, + score, + isComplete, + currentCard, + sortedCards, + + // 動作 + handleAnswer, + handleSkip, + handleRestart + } +} \ No newline at end of file diff --git a/frontend/app/review-simple/page.tsx b/frontend/app/review-simple/page.tsx index 33af53c..3247a8d 100644 --- a/frontend/app/review-simple/page.tsx +++ b/frontend/app/review-simple/page.tsx @@ -1,132 +1,25 @@ 'use client' -import { useState, useEffect } from 'react' import { Navigation } from '@/components/shared/Navigation' import './globals.css' import { SimpleFlipCard } from './components/SimpleFlipCard' import { SimpleProgress } from './components/SimpleProgress' import { SimpleResults } from './components/SimpleResults' -import { SIMPLE_CARDS, CardState, sortCardsByPriority, updateCardState } from './data' +import { SIMPLE_CARDS, CardState } from './data' +import { useReviewSession } from './hooks/useReviewSession' export default function SimpleReviewPage() { - // 延遲計數狀態管理 - const [cards, setCards] = useState(SIMPLE_CARDS) - const [currentCardIndex, setCurrentCardIndex] = useState(0) - const [score, setScore] = useState({ correct: 0, total: 0 }) - const [isComplete, setIsComplete] = useState(false) - - // 智能排序獲取當前卡片 - const sortedCards = sortCardsByPriority(cards) - const incompleteCards = sortedCards.filter((card: CardState) => !card.isCompleted) - const currentCard = incompleteCards[0] // 總是選擇優先級最高的未完成卡片 - const isLastCard = incompleteCards.length <= 1 - - // localStorage進度保存和載入 - useEffect(() => { - // 載入保存的進度 - const savedProgress = localStorage.getItem('review-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.cards) { - setCards(parsed.cards) - setScore(parsed.score || { correct: 0, total: 0 }) - console.log('📖 載入保存的複習進度') - } - } catch (error) { - console.warn('進度載入失敗:', error) - localStorage.removeItem('review-progress') - } - } - }, []) - - // 保存進度到localStorage - const saveProgress = () => { - const progress = { - cards, - score, - timestamp: new Date().toISOString() - } - localStorage.setItem('review-progress', JSON.stringify(progress)) - console.log('💾 進度已保存') - } - - // 處理答題 - 延遲計數邏輯 - const handleAnswer = (confidence: number) => { - if (!currentCard) return - - // 信心度2以上算答對 (根據規格修正) - const isCorrect = confidence >= 2 - - // 找到當前卡片在原數組中的索引 - const originalIndex = cards.findIndex(card => card.id === currentCard.id) - - if (isCorrect) { - // 答對:標記為完成 - const updatedCards = updateCardState(cards, originalIndex, { - isCompleted: true - }) - setCards(updatedCards) - } else { - // 答錯:增加答錯次數 - const updatedCards = updateCardState(cards, originalIndex, { - wrongCount: currentCard.wrongCount + 1 - }) - setCards(updatedCards) - } - - // 更新分數統計 - setScore(prevScore => ({ - correct: prevScore.correct + (isCorrect ? 1 : 0), - total: prevScore.total + 1 - })) - - // 保存進度 - saveProgress() - - // 檢查是否完成所有卡片 - const remainingCards = cards.filter((card: CardState) => !card.isCompleted) - if (remainingCards.length <= 1) { - setIsComplete(true) - } - } - - // 處理跳過 - 延遲計數邏輯 - const handleSkip = () => { - if (!currentCard) return - - // 找到當前卡片在原數組中的索引 - const originalIndex = cards.findIndex(card => card.id === currentCard.id) - - // 增加跳過次數 - const updatedCards = updateCardState(cards, originalIndex, { - skipCount: currentCard.skipCount + 1 - }) - setCards(updatedCards) - - // 保存進度 - saveProgress() - - // 檢查是否完成所有卡片 - const remainingCards = updatedCards.filter((card: CardState) => !card.isCompleted) - if (remainingCards.length === 0) { - setIsComplete(true) - } - } - - // 重新開始 - 重置所有狀態 - const handleRestart = () => { - setCards(SIMPLE_CARDS) // 重置為初始狀態 - setCurrentCardIndex(0) - setScore({ correct: 0, total: 0 }) - setIsComplete(false) - localStorage.removeItem('review-progress') // 清除保存的進度 - console.log('🔄 複習進度已重置') - } + // 使用自定義 Hook 管理複習狀態 + const { + cards, + score, + isComplete, + currentCard, + sortedCards, + handleAnswer, + handleSkip, + handleRestart + } = useReviewSession() // 顯示結果頁面 if (isComplete) {