dramaling-vocab-learning/frontend/app/review-design/page.tsx

368 lines
15 KiB
TypeScript

'use client'
import { useState, useEffect, useCallback } from 'react'
import { Navigation } from '@/components/shared/Navigation'
import { ReviewRunner } from '@/components/review/core/ReviewRunner'
import { ProgressTracker } from '@/components/review/ui/ProgressTracker'
// Store imports
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 exampleData from './example-data.json'
// 動態測試資料集配置
const TEST_DATASETS = {
empty: {
id: 'empty',
name: '清空測試',
description: '測試空資料狀態和錯誤處理',
flashcards: []
},
sample: {
id: 'sample',
name: `快速測試 (前5張卡)`,
description: '使用前5張詞卡進行快速測試',
flashcards: exampleData.data.slice(0, 5).map(card => ({
...card,
cefr: card.difficultyLevel || 'A1',
hasExampleImage: false,
primaryImageUrl: undefined,
exampleImages: []
}))
},
full: {
id: 'full',
name: `完整資料集 (${exampleData.data.length}張卡)`,
description: '使用 example-data.json 的完整真實資料',
flashcards: exampleData.data.map(card => ({
...card,
cefr: card.difficultyLevel || 'A1',
hasExampleImage: false,
primaryImageUrl: undefined,
exampleImages: []
}))
}
}
export default function ReviewDesignPage() {
const [currentDataset, setCurrentDataset] = useState<string>('')
const [isSimulating, setIsSimulating] = useState(false)
const [debugLogs, setDebugLogs] = useState<string[]>([])
// Store 狀態監控
const sessionStore = useReviewSessionStore()
const queueStore = useTestQueueStore()
const resultStore = useTestResultStore()
const dataStore = useReviewDataStore()
// 重置所有 Store
const resetAllStores = useCallback(() => {
sessionStore.resetSession()
queueStore.resetQueue()
resultStore.resetScore()
dataStore.resetData()
addLog('🔄 所有 Store 已重置')
setIsSimulating(false)
}, [])
// 匯入測試資料集
const importDataset = useCallback(async (datasetId: string) => {
const dataset = TEST_DATASETS[datasetId as keyof typeof TEST_DATASETS]
if (!dataset) return
addLog(`📁 開始匯入資料集: ${dataset.name}`)
// 重置 Store
resetAllStores()
// 模擬真實的資料載入流程
dataStore.setLoadingCards(true)
await new Promise(resolve => setTimeout(resolve, 500)) // 模擬網路延遲
// 載入資料到 ReviewDataStore
dataStore.setDueCards(dataset.flashcards)
dataStore.setLoadingCards(false)
// 觸發測試佇列初始化
queueStore.initializeTestQueue(dataset.flashcards, [])
// 設置第一張卡片
if (dataset.flashcards.length > 0) {
sessionStore.setCurrentCard(dataset.flashcards[0])
sessionStore.setMounted(true)
}
setCurrentDataset(datasetId)
addLog(`✅ 已匯入 ${dataset.flashcards.length} 張詞卡`)
addLog(`📋 產生 ${queueStore.testItems.length} 個測驗項目`)
}, [resetAllStores, dataStore, queueStore, sessionStore])
// 開始模擬
const startSimulation = useCallback(() => {
if (!currentDataset) {
addLog('❌ 請先匯入測試資料')
return
}
setIsSimulating(true)
addLog('🎮 開始複習模擬')
}, [currentDataset])
// 新增日誌
const addLog = useCallback((message: string) => {
const timestamp = new Date().toLocaleTimeString()
setDebugLogs(prev => [...prev.slice(-9), `[${timestamp}] ${message}`])
}, [])
return (
<div className="min-h-screen bg-gray-50">
<Navigation />
<div className="max-w-7xl mx-auto px-4 py-8">
<h1 className="text-3xl font-bold text-gray-900 mb-8"></h1>
<div className="grid grid-cols-1 xl:grid-cols-3 gap-8">
{/* 左側:控制面板 */}
<div className="xl:col-span-1">
<div className="bg-white rounded-lg shadow-lg p-6">
<h2 className="text-xl font-semibold mb-6"></h2>
{/* 資料管理區 */}
<div className="mb-6">
<h3 className="font-medium text-gray-900 mb-3">📁 </h3>
<div className="space-y-3">
<select
value={currentDataset}
onChange={(e) => setCurrentDataset(e.target.value)}
className="w-full p-2 border rounded-lg"
>
<option value="">...</option>
{Object.entries(TEST_DATASETS).map(([key, dataset]) => (
<option key={key} value={key}>
{dataset.name}
</option>
))}
</select>
<div className="flex gap-2">
<button
onClick={() => importDataset(currentDataset)}
disabled={!currentDataset}
className="flex-1 px-3 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 disabled:opacity-50"
>
</button>
<button
onClick={resetAllStores}
className="px-3 py-2 bg-gray-500 text-white rounded hover:bg-gray-600"
>
</button>
</div>
{currentDataset && (
<div className="p-3 bg-blue-50 rounded-lg border border-blue-200">
<p className="text-sm text-blue-700">
{TEST_DATASETS[currentDataset as keyof typeof TEST_DATASETS].description}
</p>
</div>
)}
</div>
</div>
{/* 模擬控制區 */}
<div className="mb-6">
<h3 className="font-medium text-gray-900 mb-3">🎮 </h3>
<div className="space-y-2">
<button
onClick={startSimulation}
disabled={!currentDataset || isSimulating}
className="w-full px-4 py-2 bg-green-600 text-white rounded hover:bg-green-700 disabled:opacity-50"
>
{isSimulating ? '模擬進行中...' : '開始複習模擬'}
</button>
{isSimulating && (
<button
onClick={() => {
setIsSimulating(false)
addLog('⏸️ 模擬已暫停')
}}
className="w-full px-4 py-2 bg-yellow-600 text-white rounded hover:bg-yellow-700"
>
</button>
)}
</div>
</div>
{/* Store 狀態監控 */}
<div className="mb-6">
<h3 className="font-medium text-gray-900 mb-3">📊 Store </h3>
<div className="space-y-2 text-sm">
<div className="p-2 bg-gray-50 rounded">
<strong>Session:</strong>
<span className="ml-2">
{sessionStore.currentCard ? `卡片: ${sessionStore.currentCard.word}` : '無卡片'}
</span>
</div>
<div className="p-2 bg-gray-50 rounded">
<strong>Queue:</strong>
<span className="ml-2">
{queueStore.testItems.length} | : {queueStore.currentTestIndex}
</span>
</div>
<div className="p-2 bg-gray-50 rounded">
<strong>Result:</strong>
<span className="ml-2">
{resultStore.score.correct} / {resultStore.score.total}
</span>
</div>
<div className="p-2 bg-gray-50 rounded">
<strong>Data:</strong>
<span className="ml-2">
{dataStore.dueCards.length}
{dataStore.isLoadingCards && ' (載入中...)'}
</span>
</div>
</div>
</div>
{/* 調試日誌 */}
<div>
<h3 className="font-medium text-gray-900 mb-3">🐛 調</h3>
<div className="bg-gray-900 text-green-400 p-3 rounded-lg text-xs font-mono h-40 overflow-y-auto">
{debugLogs.length === 0 ? (
<div className="text-gray-500">...</div>
) : (
debugLogs.map((log, index) => (
<div key={index} className="mb-1">
{log}
</div>
))
)}
</div>
</div>
</div>
</div>
{/* 右側:複習模擬器 */}
<div className="xl:col-span-2">
<div className="bg-white rounded-lg shadow-lg">
<div className="p-6 border-b">
<h2 className="text-xl font-semibold"></h2>
<p className="text-gray-600 mt-1">
使 ReviewRunner Store
</p>
</div>
<div className="p-6">
{!currentDataset ? (
<div className="text-center py-12">
<div className="text-gray-400 text-6xl mb-4">📚</div>
<h3 className="text-lg font-medium text-gray-900 mb-2"></h3>
<p className="text-gray-600"></p>
</div>
) : !isSimulating ? (
<div className="text-center py-12">
<div className="text-blue-400 text-6xl mb-4">🎮</div>
<h3 className="text-lg font-medium text-gray-900 mb-2"></h3>
<p className="text-gray-600 mb-4">
{TEST_DATASETS[currentDataset as keyof typeof TEST_DATASETS].name}
</p>
<button
onClick={startSimulation}
className="px-6 py-3 bg-green-600 text-white rounded-lg hover:bg-green-700"
>
</button>
</div>
) : (
<div>
{/* 真實進度條 */}
<div className="mb-6">
<ProgressTracker
completedTests={queueStore.completedTests}
totalTests={queueStore.totalTests}
onShowTaskList={() => addLog('📋 顯示任務清單')}
/>
</div>
{/* 真實 ReviewRunner 組件 */}
<ReviewRunner />
{/* 模擬控制 */}
<div className="mt-6 pt-6 border-t">
<div className="flex justify-center gap-3">
<button
onClick={() => {
setIsSimulating(false)
addLog('⏸️ 模擬已暫停')
}}
className="px-4 py-2 bg-yellow-600 text-white rounded hover:bg-yellow-700"
>
</button>
<button
onClick={resetAllStores}
className="px-4 py-2 bg-red-600 text-white rounded hover:bg-red-700"
>
</button>
</div>
</div>
</div>
)}
</div>
</div>
{/* 測驗佇列視覺化 */}
{isSimulating && queueStore.testItems.length > 0 && (
<div className="bg-white rounded-lg shadow-lg mt-6 p-6">
<h3 className="font-medium text-gray-900 mb-4">📋 </h3>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3">
{queueStore.testItems.map((test, index) => (
<div
key={test.id}
className={`p-3 rounded-lg border ${
index === queueStore.currentTestIndex
? 'border-blue-500 bg-blue-50'
: test.isCompleted
? 'border-green-500 bg-green-50'
: test.isSkipped
? 'border-yellow-500 bg-yellow-50'
: test.isIncorrect
? 'border-red-500 bg-red-50'
: 'border-gray-200 bg-gray-50'
}`}
>
<div className="flex justify-between items-start">
<div>
<div className="font-medium text-sm">{test.word}</div>
<div className="text-xs text-gray-600">{test.testType}</div>
</div>
<div className="text-xs">
{index === queueStore.currentTestIndex && '👆'}
{test.isCompleted && '✅'}
{test.isSkipped && '⏭️'}
{test.isIncorrect && '❌'}
{!test.isCompleted && !test.isSkipped && !test.isIncorrect && index !== queueStore.currentTestIndex && '⏳'}
</div>
</div>
<div className="mt-1 text-xs text-gray-500">
: {test.priority || 100}
</div>
</div>
))}
</div>
</div>
)}
</div>
</div>
</div>
</div>
)
}