393 lines
16 KiB
TypeScript
393 lines
16 KiB
TypeScript
'use client'
|
||
|
||
import { Navigation } from '@/components/shared/Navigation'
|
||
import { FlipMemory } from '@/components/review/quiz/FlipMemory'
|
||
import { VocabChoiceQuiz } from '@/components/review/quiz/VocabChoiceQuiz'
|
||
import { SentenceSpeakingQuiz } from '@/components/review/quiz/SentenceSpeakingQuiz'
|
||
import { QuizProgress } from '@/components/review/ui/QuizProgress'
|
||
import { QuizResult } from '@/components/review/quiz/QuizResult'
|
||
import { useReviewSession } from '@/hooks/review/useReviewSession'
|
||
|
||
export default function ReviewPage() {
|
||
// 使用重構後的 Hook 管理線性複習狀態
|
||
const {
|
||
quizItems,
|
||
score,
|
||
isComplete,
|
||
currentQuizItem,
|
||
currentCard,
|
||
vocabOptions,
|
||
totalQuizItems,
|
||
completedQuizItems,
|
||
handleAnswer,
|
||
handleSkip,
|
||
handleRestart,
|
||
isLoading,
|
||
error,
|
||
flashcards,
|
||
totalFlashcardsCount
|
||
} = useReviewSession()
|
||
|
||
// 除錯日誌 - 檢查狀態
|
||
console.log('🔍 Review Page 狀態檢查:', {
|
||
isLoading,
|
||
error,
|
||
flashcardsLength: flashcards.length,
|
||
totalFlashcardsCount,
|
||
currentQuizItem: currentQuizItem?.id,
|
||
currentCard: currentCard?.word,
|
||
isComplete
|
||
})
|
||
|
||
// 顯示載入狀態
|
||
if (isLoading) {
|
||
return (
|
||
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100">
|
||
<Navigation />
|
||
<div className="py-8">
|
||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||
<div className="bg-white rounded-xl shadow-lg p-8 text-center">
|
||
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto mb-4"></div>
|
||
<h2 className="text-xl font-semibold text-gray-700">載入詞卡中...</h2>
|
||
<p className="text-gray-500 mt-2">正在從後端獲取您的複習詞卡</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
// 顯示錯誤狀態
|
||
if (error) {
|
||
const isAuthError = error.includes('登入已過期') || error.includes('認證')
|
||
|
||
return (
|
||
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100">
|
||
<Navigation />
|
||
<div className="py-8">
|
||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||
<div className="bg-white rounded-xl shadow-lg p-8 text-center">
|
||
<div className={`text-4xl mb-4 ${isAuthError ? 'text-yellow-500' : 'text-red-500'}`}>
|
||
{isAuthError ? '🔒' : '⚠️'}
|
||
</div>
|
||
<h2 className={`text-xl font-semibold mb-2 ${isAuthError ? 'text-yellow-700' : 'text-red-700'}`}>
|
||
{isAuthError ? '需要重新登入' : '載入失敗'}
|
||
</h2>
|
||
<p className="text-gray-600 mb-6">{error}</p>
|
||
|
||
<div className="flex flex-col sm:flex-row gap-3 justify-center">
|
||
{isAuthError ? (
|
||
<button
|
||
onClick={() => window.location.href = '/login'}
|
||
className="px-8 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors font-semibold"
|
||
>
|
||
前往登入
|
||
</button>
|
||
) : (
|
||
<button
|
||
onClick={handleRestart}
|
||
className="px-6 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
|
||
>
|
||
重新載入
|
||
</button>
|
||
)}
|
||
|
||
<button
|
||
onClick={() => window.location.href = '/'}
|
||
className="px-6 py-2 bg-gray-600 text-white rounded-lg hover:bg-gray-700 transition-colors"
|
||
>
|
||
回到首頁
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
// 情境 1: 新用戶 - 一張詞卡都沒有
|
||
if (!isLoading && !error && totalFlashcardsCount === 0) {
|
||
return (
|
||
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100">
|
||
<Navigation />
|
||
<div className="py-8">
|
||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||
<div className="bg-white rounded-xl shadow-lg p-8 text-center">
|
||
{/* 歡迎圖標 */}
|
||
<div className="mb-6">
|
||
<div className="w-24 h-24 bg-gradient-to-br from-blue-400 to-blue-600 rounded-full flex items-center justify-center mx-auto mb-4">
|
||
<span className="text-4xl text-white">👋</span>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 歡迎標題 */}
|
||
<h1 className="text-3xl font-bold text-gray-900 mb-4">
|
||
歡迎來到 DramaLing!
|
||
</h1>
|
||
|
||
{/* 說明文字 */}
|
||
<p className="text-lg text-gray-600 mb-8">
|
||
開始您的英語學習之旅,建立第一張詞卡來開始學習吧!
|
||
</p>
|
||
|
||
{/* 功能介紹卡片 */}
|
||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mb-8">
|
||
<div className="bg-gradient-to-br from-green-50 to-green-100 rounded-lg p-6">
|
||
<div className="text-2xl text-green-600 mb-2">🎯</div>
|
||
<h3 className="font-semibold text-gray-900">智能學習</h3>
|
||
<p className="text-sm text-gray-600 mt-2">
|
||
AI 驅動的個人化詞彙學習系統
|
||
</p>
|
||
</div>
|
||
|
||
<div className="bg-gradient-to-br from-yellow-50 to-yellow-100 rounded-lg p-6">
|
||
<div className="text-2xl text-yellow-600 mb-2">🧠</div>
|
||
<h3 className="font-semibold text-gray-900">科學記憶</h3>
|
||
<p className="text-sm text-gray-600 mt-2">
|
||
基於遺忘曲線的複習提醒
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 主要行動按鈕 */}
|
||
<div className="mb-6">
|
||
<button
|
||
onClick={() => window.location.href = '/generate'}
|
||
className="px-10 py-4 bg-gradient-to-r from-blue-600 to-blue-700 text-white rounded-lg hover:from-blue-700 hover:to-blue-800 transition-all font-semibold text-lg flex items-center justify-center gap-3 mx-auto"
|
||
>
|
||
<span className="text-xl">🚀</span>
|
||
建立第一張詞卡
|
||
</button>
|
||
</div>
|
||
|
||
{/* 次要功能 */}
|
||
<div className="flex flex-col sm:flex-row gap-3 justify-center">
|
||
<button
|
||
onClick={() => window.location.href = '/flashcards'}
|
||
className="px-6 py-2 bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200 transition-colors font-medium"
|
||
>
|
||
瀏覽詞卡功能
|
||
</button>
|
||
</div>
|
||
|
||
{/* 學習提示 */}
|
||
<div className="mt-8 p-4 bg-blue-50 rounded-lg border border-blue-200">
|
||
<p className="text-sm text-blue-800">
|
||
💡 <strong>開始提示</strong>: 建議從日常生活中的詞彙開始,
|
||
或輸入您感興趣的英文句子讓 AI 協助分析!
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
// 情境 2: 有詞卡但都已訓練完成
|
||
if (!isLoading && !error && totalFlashcardsCount && totalFlashcardsCount > 0 && flashcards.length === 0) {
|
||
return (
|
||
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100">
|
||
<Navigation />
|
||
<div className="py-8">
|
||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||
<div className="bg-white rounded-xl shadow-lg p-8 text-center">
|
||
{/* 慶祝圖標 */}
|
||
<div className="mb-6">
|
||
<div className="w-24 h-24 bg-gradient-to-br from-green-400 to-green-600 rounded-full flex items-center justify-center mx-auto mb-4">
|
||
<span className="text-4xl text-white">🎉</span>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 主要標題 */}
|
||
<h1 className="text-3xl font-bold text-gray-900 mb-4">
|
||
太棒了!所有詞卡都已掌握
|
||
</h1>
|
||
|
||
{/* 次標題 */}
|
||
<p className="text-lg text-gray-600 mb-4">
|
||
您已完成所有 <span className="font-semibold text-green-600">{totalFlashcardsCount}</span> 張詞卡的學習!
|
||
</p>
|
||
<p className="text-md text-gray-500 mb-8">
|
||
目前沒有需要複習的詞卡,您的學習進度非常優秀!
|
||
</p>
|
||
|
||
{/* 統計卡片 */}
|
||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
|
||
<div className="bg-gradient-to-br from-blue-50 to-blue-100 rounded-lg p-6">
|
||
<div className="text-2xl text-blue-600 mb-2">📚</div>
|
||
<h3 className="font-semibold text-gray-900">持續學習</h3>
|
||
<p className="text-sm text-gray-600 mt-2">
|
||
保持每日複習習慣
|
||
</p>
|
||
</div>
|
||
|
||
<div className="bg-gradient-to-br from-purple-50 to-purple-100 rounded-lg p-6">
|
||
<div className="text-2xl text-purple-600 mb-2">📈</div>
|
||
<h3 className="font-semibold text-gray-900">查看進度</h3>
|
||
<p className="text-sm text-gray-600 mt-2">
|
||
檢視學習統計數據
|
||
</p>
|
||
</div>
|
||
|
||
<div className="bg-gradient-to-br from-green-50 to-green-100 rounded-lg p-6">
|
||
<div className="text-2xl text-green-600 mb-2">➕</div>
|
||
<h3 className="font-semibold text-gray-900">擴展詞庫</h3>
|
||
<p className="text-sm text-gray-600 mt-2">
|
||
新增更多詞彙挑戰
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 行動按鈕 */}
|
||
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
||
<button
|
||
onClick={() => window.location.href = '/generate'}
|
||
className="px-8 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors font-semibold flex items-center justify-center gap-2"
|
||
>
|
||
<span>➕</span>
|
||
新增詞卡
|
||
</button>
|
||
|
||
<button
|
||
onClick={() => window.location.href = '/flashcards'}
|
||
className="px-8 py-3 bg-gray-600 text-white rounded-lg hover:bg-gray-700 transition-colors font-semibold flex items-center justify-center gap-2"
|
||
>
|
||
<span>📋</span>
|
||
管理詞卡
|
||
</button>
|
||
</div>
|
||
|
||
{/* 提示文字 */}
|
||
<div className="mt-8 p-4 bg-yellow-50 rounded-lg border border-yellow-200">
|
||
<p className="text-sm text-yellow-800">
|
||
💡 <strong>小提示</strong>: 詞卡會根據遺忘曲線算法自動安排複習時間。
|
||
繼續保持學習,明天可能會有新的複習內容!
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
// 顯示結果頁面
|
||
if (isComplete) {
|
||
return (
|
||
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100">
|
||
<Navigation />
|
||
<div className="py-8">
|
||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||
<QuizResult
|
||
score={score}
|
||
totalCards={flashcards.length}
|
||
onRestart={handleRestart}
|
||
/>
|
||
|
||
{/* 線性測驗完成統計 */}
|
||
<div className="mt-6 bg-white rounded-lg shadow p-6">
|
||
<h3 className="text-lg font-semibold text-gray-900 mb-4">測驗統計</h3>
|
||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 text-center">
|
||
<div>
|
||
<div className="text-2xl font-bold text-blue-600">{completedQuizItems}</div>
|
||
<div className="text-sm text-gray-600">完成測驗項目</div>
|
||
</div>
|
||
<div>
|
||
<div className="text-2xl font-bold text-green-600">{flashcards.length}</div>
|
||
<div className="text-sm text-gray-600">練習詞卡數</div>
|
||
</div>
|
||
<div>
|
||
<div className="text-2xl font-bold text-purple-600">
|
||
{score.total > 0 ? Math.round((score.correct / score.total) * 100) : 0}%
|
||
</div>
|
||
<div className="text-sm text-gray-600">正確率</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
// 主要線性測驗頁面
|
||
// 只有在有可用測驗項目時才顯示測驗界面
|
||
if (!isLoading && !error && totalFlashcardsCount !== null && totalFlashcardsCount > 0 && flashcards.length > 0 && currentQuizItem && currentCard) {
|
||
return (
|
||
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100">
|
||
<Navigation />
|
||
|
||
<div className="py-8">
|
||
<div className="max-w-4xl mx-auto px-4">
|
||
{/* 使用簡化的 ReviewProgress 組件 */}
|
||
<QuizProgress
|
||
currentQuizItem={currentQuizItem}
|
||
totalQuizItems={totalQuizItems}
|
||
completedQuizItems={completedQuizItems}
|
||
score={score}
|
||
/>
|
||
|
||
{/* 根據當前測驗項目類型渲染對應組件 */}
|
||
{currentQuizItem && currentCard && (
|
||
<>
|
||
{currentQuizItem.quizType === 'flip-card' && (
|
||
<FlipMemory
|
||
card={currentCard}
|
||
onAnswer={handleAnswer}
|
||
onSkip={handleSkip}
|
||
/>
|
||
)}
|
||
|
||
{currentQuizItem.quizType === 'vocab-choice' && (
|
||
<VocabChoiceQuiz
|
||
card={currentCard}
|
||
options={vocabOptions}
|
||
onAnswer={handleAnswer}
|
||
onSkip={handleSkip}
|
||
/>
|
||
)}
|
||
|
||
{currentQuizItem.quizType === 'sentence-speaking' && (
|
||
<SentenceSpeakingQuiz
|
||
card={currentCard}
|
||
onAnswer={handleAnswer}
|
||
onSkip={handleSkip}
|
||
/>
|
||
)}
|
||
</>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
// Fallback 狀態 - 處理其他未預期情況
|
||
return (
|
||
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100">
|
||
<Navigation />
|
||
<div className="py-8">
|
||
<div className="max-w-4xl mx-auto px-4">
|
||
<div className="bg-white rounded-xl shadow-lg p-8 text-center">
|
||
<div className="text-4xl mb-4">🤔</div>
|
||
<h2 className="text-xl font-semibold text-gray-700 mb-2">正在準備學習內容</h2>
|
||
<p className="text-gray-600 mb-4">
|
||
系統正在載入您的學習資料,請稍候片刻。
|
||
</p>
|
||
<p className="text-sm text-gray-500 mb-6">
|
||
如果問題持續,請嘗試重新載入頁面。
|
||
</p>
|
||
<button
|
||
onClick={handleRestart}
|
||
className="px-6 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
|
||
>
|
||
重新載入
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)
|
||
} |