diff --git a/frontend/app/review-design/page.tsx b/frontend/app/review-design/page.tsx index b0921aa..e8e7077 100644 --- a/frontend/app/review-design/page.tsx +++ b/frontend/app/review-design/page.tsx @@ -1,272 +1,364 @@ 'use client' -import { useState, useEffect } from 'react' +import { useState, useEffect, useCallback } from 'react' import { Navigation } from '@/components/shared/Navigation' -import { - FlipMemoryTest, - VocabChoiceTest, - SentenceFillTest, - SentenceReorderTest, - VocabListeningTest, - SentenceListeningTest, - SentenceSpeakingTest -} from '@/components/review/review-tests' +import { ReviewRunner } from '@/components/review/ReviewRunner' +import { ProgressTracker } from '@/components/review/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' -export default function ReviewTestsPage() { - const [logs, setLogs] = useState([]) - const [activeTab, setActiveTab] = useState('FlipMemoryTest') - const [currentCardIndex, setCurrentCardIndex] = useState(0) +// 動態測試資料集配置 +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: [] + })) + } +} - // 測驗組件清單 - const testComponents = [ - { id: 'FlipMemoryTest', name: '翻卡記憶測試', color: 'bg-blue-50' }, - { id: 'VocabChoiceTest', name: '詞彙選擇測試', color: 'bg-green-50' }, - { id: 'SentenceFillTest', name: '句子填空測試', color: 'bg-yellow-50' }, - { id: 'SentenceReorderTest', name: '句子重排測試', color: 'bg-purple-50' }, - { id: 'VocabListeningTest', name: '詞彙聽力測試', color: 'bg-red-50' }, - { id: 'SentenceListeningTest', name: '句子聽力測試', color: 'bg-indigo-50' }, - { id: 'SentenceSpeakingTest', name: '句子口說測試', color: 'bg-pink-50' } - ] +export default function ReviewDesignPage() { + const [currentDataset, setCurrentDataset] = useState('') + const [isSimulating, setIsSimulating] = useState(false) + const [debugLogs, setDebugLogs] = useState([]) - // 添加日誌函數 - const addLog = (message: 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() - setLogs(prev => [`[${activeTab}] [${timestamp}] ${message}`, ...prev.slice(0, 9)]) - } - - // 從 API 響應格式獲取當前卡片資料 - const flashcardsData = exampleData.data || [] - const currentCard = flashcardsData[currentCardIndex] || flashcardsData[0] - - // 轉換為組件所需格式 - const mockCardData = currentCard ? { - word: currentCard.word, - definition: currentCard.definition, - example: currentCard.example, - filledQuestionText: currentCard.filledQuestionText, - exampleTranslation: currentCard.exampleTranslation, - pronunciation: currentCard.pronunciation, - synonyms: currentCard.synonyms || [], - cefr: currentCard.difficultyLevel || 'A1', - translation: currentCard.translation, - // 從 flashcardExampleImages 提取圖片URL - exampleImage: currentCard.flashcardExampleImages?.[0]?.exampleImage ? - `http://localhost:5008/images/examples/${currentCard.flashcardExampleImages[0].exampleImage.relativePath}` : - undefined - } : { - word: "loading...", - definition: "Loading...", - example: "Loading...", - filledQuestionText: undefined, - exampleTranslation: "載入中...", - pronunciation: "", - synonyms: [], - cefr: "A1", - translation: "載入中", - exampleImage: undefined - } - - // 選項題選項 - 從資料中生成 - const generateVocabChoiceOptions = () => { - if (!currentCard) return ["loading"] - const correctAnswer = currentCard.word - const otherWords = flashcardsData - .filter(card => card.word !== correctAnswer) - .slice(0, 3) - .map(card => card.word) - return [correctAnswer, ...otherWords].sort(() => Math.random() - 0.5) - } - - const vocabChoiceOptions = generateVocabChoiceOptions() - - // 回調函數 - const handleConfidenceSubmit = (level: number) => { - addLog(`FlipMemoryTest: 信心等級 ${level}`) - } - - const handleAnswer = (answer: string) => { - addLog(`答案提交: ${answer}`) - } - - const handleReportError = () => { - addLog('回報錯誤') - } - - const handleImageClick = (image: string) => { - addLog(`圖片點擊: ${image}`) - } + setDebugLogs(prev => [...prev.slice(-9), `[${timestamp}] ${message}`]) + }, []) return ( -
+
-
- {/* 頁面標題 */} -
-

Review 組件設計

-

所有 review-tests 組件的 UI 設計頁面

+
+

複習系統設計工具

- {/* 卡片切換控制 */} -
- - - 卡片 {currentCardIndex + 1} / {flashcardsData.length} - {currentCard?.word || 'loading'} - - -
-
+
+ {/* 左側:控制面板 */} +
+
+

控制面板

- {/* Tab 導航 */} -
-
-
- {testComponents.map((component) => ( - - ))} + {/* 資料管理區 */} +
+

📁 測試資料管理

+
+ + +
+ + +
+ + {currentDataset && ( +
+

+ {TEST_DATASETS[currentDataset as keyof typeof TEST_DATASETS].description} +

+
+ )} +
+
+ + {/* 模擬控制區 */} +
+

🎮 模擬控制

+
+ + + {isSimulating && ( + + )} +
+
+ + {/* Store 狀態監控 */} +
+

📊 Store 狀態

+
+
+ Session: + + {sessionStore.currentCard ? `卡片: ${sessionStore.currentCard.word}` : '無卡片'} + +
+
+ Queue: + + {queueStore.testItems.length}個測驗 | 當前: {queueStore.currentTestIndex} + +
+
+ Result: + + {resultStore.score.correct}✓ / {resultStore.score.total} + +
+
+ Data: + + {dataStore.dueCards.length}張詞卡 + {dataStore.isLoadingCards && ' (載入中...)'} + +
+
+
+ + {/* 調試日誌 */} +
+

🐛 調試日誌

+
+ {debugLogs.length === 0 ? ( +
等待操作...
+ ) : ( + debugLogs.map((log, index) => ( +
+ {log} +
+ )) + )} +
+
-
+ {/* 右側:複習模擬器 */} +
+
+
+

複習模擬器

+

+ 使用真實的 ReviewRunner 組件和 Store 狀態 +

+
- {/* 當前測驗組件展示 */} -
-
-

{activeTab}

-

{testComponents.find(c => c.id === activeTab)?.name}

-
-
- {/* 條件渲染當前選中的測驗組件 */} - {activeTab === 'FlipMemoryTest' && ( - - )} +
+ {!currentDataset ? ( +
+
📚
+

開始設計測試

+

請先從左側控制面板匯入測試資料

+
+ ) : !isSimulating ? ( +
+
🎮
+

準備開始模擬

+

+ 已載入 {TEST_DATASETS[currentDataset as keyof typeof TEST_DATASETS].name} +

+ +
+ ) : ( +
+ {/* 真實進度條 */} +
+ addLog('📋 顯示任務清單')} + /> +
- {activeTab === 'VocabChoiceTest' && ( - - )} + {/* 真實 ReviewRunner 組件 */} + - {activeTab === 'SentenceFillTest' && ( - - )} + {/* 模擬控制 */} +
+
+ + +
+
+
+ )} +
+
- {activeTab === 'SentenceReorderTest' && ( - - )} - - {activeTab === 'VocabListeningTest' && ( - - )} - - {activeTab === 'SentenceListeningTest' && ( - - )} - - {activeTab === 'SentenceSpeakingTest' && ( - - )} -
-
- - {/* 操作日誌區域 */} -
-

操作日誌

-
- {logs.length === 0 ? ( -

無操作記錄

- ) : ( - logs.map((log, index) => ( -
- {log} + {/* 測驗佇列視覺化 */} + {isSimulating && queueStore.testItems.length > 0 && ( +
+

📋 測驗佇列視覺化

+
+ {queueStore.testItems.map((test, index) => ( +
+
+
+
{test.word}
+
{test.testType}
+
+
+ {index === queueStore.currentTestIndex && '👆'} + {test.isCompleted && '✅'} + {test.isSkipped && '⏭️'} + {test.isIncorrect && '❌'} + {!test.isCompleted && !test.isSkipped && !test.isIncorrect && index !== queueStore.currentTestIndex && '⏳'} +
+
+
+ 優先級: {test.priority || 100} +
+
+ ))}
- )) +
)}