254 lines
7.5 KiB
TypeScript
254 lines
7.5 KiB
TypeScript
'use client'
|
||
|
||
import { useEffect } from 'react'
|
||
import { useRouter } from 'next/navigation'
|
||
import { Navigation } from '@/components/shared/Navigation'
|
||
import LearningComplete from '@/components/flashcards/LearningComplete'
|
||
import { Modal } from '@/components/ui/Modal'
|
||
|
||
// 新架構組件
|
||
import { ProgressTracker } from '@/components/review/ui/ProgressTracker'
|
||
import { TaskListModal } from '@/components/review/modals/TaskListModal'
|
||
import { LoadingStates } from '@/components/review/ui/LoadingStates'
|
||
import { ReviewRunner } from '@/components/review/core/ReviewRunner'
|
||
|
||
// 狀態管理
|
||
import { useReviewSessionStore } from '@/store/review/useReviewSessionStore'
|
||
import { useTestQueueStore } from '@/store/review/useTestQueueStore'
|
||
import { useTestResultStore } from '@/store/review/useTestResultStore'
|
||
import { useReviewDataStore } from '@/store/review/useReviewDataStore'
|
||
import { useReviewUIStore } from '@/store/review/useReviewUIStore'
|
||
import { ReviewService } from '@/lib/services/review/reviewService'
|
||
|
||
export default function LearnPage() {
|
||
const router = useRouter()
|
||
|
||
// Zustand stores
|
||
const { mounted, currentCard, error, setMounted, resetSession: resetSessionState } = useReviewSessionStore()
|
||
const {
|
||
testItems,
|
||
completedTests,
|
||
totalTests,
|
||
initializeTestQueue,
|
||
resetQueue
|
||
} = useTestQueueStore()
|
||
const { score, resetScore } = useTestResultStore()
|
||
const {
|
||
dueCards,
|
||
showComplete,
|
||
showNoDueCards,
|
||
isLoadingCards,
|
||
loadDueCards,
|
||
resetData,
|
||
setShowComplete
|
||
} = useReviewDataStore()
|
||
|
||
const {
|
||
showTaskListModal,
|
||
showReportModal,
|
||
modalImage,
|
||
reportReason,
|
||
reportingCard,
|
||
setShowTaskListModal,
|
||
closeReportModal,
|
||
closeImageModal,
|
||
setReportReason
|
||
} = useReviewUIStore()
|
||
|
||
// 初始化
|
||
useEffect(() => {
|
||
setMounted(true)
|
||
initializeSession()
|
||
}, [])
|
||
|
||
// 初始化學習會話
|
||
const initializeSession = async () => {
|
||
try {
|
||
await loadDueCards()
|
||
} catch (error) {
|
||
console.error('初始化複習會話失敗:', error)
|
||
}
|
||
}
|
||
|
||
// 監聽dueCards變化,初始化測試隊列
|
||
useEffect(() => {
|
||
if (dueCards.length > 0) {
|
||
const initQueue = async () => {
|
||
try {
|
||
const cardIds = dueCards.map(c => c.id)
|
||
const completedTests = await ReviewService.loadCompletedTests(cardIds)
|
||
initializeTestQueue(dueCards, completedTests)
|
||
} catch (error) {
|
||
console.error('初始化測試隊列失敗:', error)
|
||
}
|
||
}
|
||
initQueue()
|
||
}
|
||
}, [dueCards, initializeTestQueue])
|
||
|
||
// 監聽測試隊列變化,設置當前卡片
|
||
useEffect(() => {
|
||
if (testItems.length > 0 && dueCards.length > 0) {
|
||
const currentTestItem = testItems.find(item => item.isCurrent)
|
||
if (currentTestItem) {
|
||
const card = dueCards.find(c => c.id === currentTestItem.cardId)
|
||
if (card) {
|
||
const { setCurrentCard } = useReviewSessionStore.getState()
|
||
setCurrentCard(card)
|
||
}
|
||
}
|
||
}
|
||
}, [testItems, dueCards])
|
||
|
||
// 監聽測試完成狀態
|
||
useEffect(() => {
|
||
if (totalTests > 0 && completedTests >= totalTests) {
|
||
setShowComplete(true)
|
||
}
|
||
}, [completedTests, totalTests, setShowComplete])
|
||
|
||
// 重新開始
|
||
const handleRestart = async () => {
|
||
resetSessionState()
|
||
resetQueue()
|
||
resetScore()
|
||
resetData()
|
||
await initializeSession()
|
||
}
|
||
|
||
// 載入狀態
|
||
if (!mounted || isLoadingCards) {
|
||
return (
|
||
<LoadingStates
|
||
isLoadingCard={isLoadingCards}
|
||
isAutoSelecting={true}
|
||
/>
|
||
)
|
||
}
|
||
|
||
if (showNoDueCards) {
|
||
return (
|
||
<LoadingStates
|
||
showNoDueCards={true}
|
||
onRestart={handleRestart}
|
||
/>
|
||
)
|
||
}
|
||
|
||
if (!currentCard) {
|
||
return <LoadingStates isLoadingCard={true} />
|
||
}
|
||
|
||
return (
|
||
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100">
|
||
<Navigation />
|
||
|
||
<div className="max-w-4xl mx-auto px-4 py-8">
|
||
{/* 進度追蹤 */}
|
||
<ProgressTracker
|
||
completedTests={completedTests}
|
||
totalTests={totalTests}
|
||
onShowTaskList={() => setShowTaskListModal(true)}
|
||
/>
|
||
|
||
{/* 測驗執行器 */}
|
||
<ReviewRunner />
|
||
|
||
{/* 任務清單Modal */}
|
||
<TaskListModal
|
||
isOpen={showTaskListModal}
|
||
onClose={() => setShowTaskListModal(false)}
|
||
testItems={testItems}
|
||
completedTests={completedTests}
|
||
totalTests={totalTests}
|
||
/>
|
||
|
||
{/* 學習完成 */}
|
||
{showComplete && (
|
||
<LearningComplete
|
||
score={score}
|
||
mode={'flip-memory'} // 可以從store獲取
|
||
onRestart={handleRestart}
|
||
onBackToDashboard={() => router.push('/dashboard')}
|
||
/>
|
||
)}
|
||
|
||
{/* 圖片Modal */}
|
||
{modalImage && (
|
||
<div
|
||
className="fixed inset-0 bg-black bg-opacity-75 flex items-center justify-center z-50"
|
||
onClick={closeImageModal}
|
||
>
|
||
<div className="relative max-w-4xl max-h-[90vh] mx-4">
|
||
<img
|
||
src={modalImage}
|
||
alt="放大圖片"
|
||
className="max-w-full max-h-full rounded-lg"
|
||
/>
|
||
<button
|
||
onClick={closeImageModal}
|
||
className="absolute top-4 right-4 bg-black bg-opacity-50 text-white p-2 rounded-full hover:bg-opacity-75"
|
||
>
|
||
✕
|
||
</button>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* 錯誤回報Modal */}
|
||
<Modal
|
||
isOpen={showReportModal}
|
||
onClose={closeReportModal}
|
||
title="回報錯誤"
|
||
size="md"
|
||
>
|
||
<div className="p-6">
|
||
<div className="mb-4">
|
||
<p className="text-sm text-gray-600 mb-2">
|
||
單字:{reportingCard?.word}
|
||
</p>
|
||
</div>
|
||
|
||
<div className="mb-4">
|
||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||
錯誤類型
|
||
</label>
|
||
<select
|
||
value={reportReason}
|
||
onChange={(e) => setReportReason(e.target.value)}
|
||
className="w-full p-2 border border-gray-300 rounded-md focus:ring-blue-500 focus:border-blue-500"
|
||
>
|
||
<option value="">請選擇錯誤類型</option>
|
||
<option value="translation">翻譯錯誤</option>
|
||
<option value="definition">定義錯誤</option>
|
||
<option value="pronunciation">發音錯誤</option>
|
||
<option value="example">例句錯誤</option>
|
||
<option value="image">圖片錯誤</option>
|
||
<option value="other">其他</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div className="flex gap-2">
|
||
<button
|
||
onClick={closeReportModal}
|
||
className="flex-1 px-4 py-2 border border-gray-300 text-gray-700 rounded-md hover:bg-gray-50"
|
||
>
|
||
取消
|
||
</button>
|
||
<button
|
||
onClick={() => {
|
||
console.log('Report submitted:', { card: reportingCard, reason: reportReason })
|
||
closeReportModal()
|
||
}}
|
||
disabled={!reportReason}
|
||
className="flex-1 px-4 py-2 bg-red-600 text-white rounded-md hover:bg-red-700 disabled:opacity-50 disabled:cursor-not-allowed"
|
||
>
|
||
送出回報
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</Modal>
|
||
</div>
|
||
</div>
|
||
)
|
||
} |