diff --git a/frontend/app/review-simple/components/SimpleFlipCard.tsx b/frontend/app/review-simple/components/SimpleFlipCard.tsx index dda1122..594e6de 100644 --- a/frontend/app/review-simple/components/SimpleFlipCard.tsx +++ b/frontend/app/review-simple/components/SimpleFlipCard.tsx @@ -1,11 +1,11 @@ 'use client' import { useState, useRef, useEffect, useCallback } from 'react' -import { SimpleCard, CONFIDENCE_LEVELS } from '../data' +import { ApiFlashcard } from '../data' import { SimpleTestHeader } from './SimpleTestHeader' interface SimpleFlipCardProps { - card: SimpleCard + card: ApiFlashcard onAnswer: (confidence: number) => void } @@ -16,37 +16,43 @@ export function SimpleFlipCard({ card, onAnswer }: SimpleFlipCardProps) { const frontRef = useRef(null) const backRef = useRef(null) - // 智能高度計算 (復用原有邏輯) + // 判斷是否已答題(選擇了信心等級) + const hasAnswered = selectedConfidence !== null + + // 智能高度計算 (完全復用您的原始邏輯) useEffect(() => { const updateCardHeight = () => { if (backRef.current) { const backHeight = backRef.current.scrollHeight - // 響應式最小高度設定 (復用原有響應式邏輯) + // 響應式最小高度設定 const minHeightByScreen = window.innerWidth <= 480 ? 300 : window.innerWidth <= 768 ? 350 : 400 + // 以背面內容高度為準,不設最大高度限制 const finalHeight = Math.max(minHeightByScreen, backHeight) setCardHeight(finalHeight) } } + // 延遲執行以確保內容已渲染 const timer = setTimeout(updateCardHeight, 100) - window.addEventListener('resize', updateCardHeight) + window.addEventListener('resize', updateCardHeight) return () => { clearTimeout(timer) window.removeEventListener('resize', updateCardHeight) } - }, [card.word, card.definition, card.example]) + }, [card.word, card.definition, card.example, card.synonyms]) const handleFlip = useCallback(() => { setIsFlipped(!isFlipped) }, [isFlipped]) const handleConfidenceSelect = useCallback((level: number) => { + if (hasAnswered) return setSelectedConfidence(level) - }, []) + }, [hasAnswered]) const handleSubmit = () => { if (selectedConfidence) { @@ -57,13 +63,11 @@ export function SimpleFlipCard({ card, onAnswer }: SimpleFlipCardProps) { } } - const hasAnswered = selectedConfidence !== null - return ( -
- {/* 完全復用原FlipMemoryTest的精確設計 */} +
+ {/* 翻卡容器 - 完全復用您的設計 */}
- {/* 正面 - 完全復用原設計 */} + {/* 正面 */}
+

+ 點擊卡片翻面,根據你對單字的熟悉程度進行自我評估: +

+

{card.word}

@@ -105,14 +113,11 @@ export function SimpleFlipCard({ card, onAnswer }: SimpleFlipCardProps) {
-

- 點擊卡片翻面 -

- {/* 背面 - 完全復用原設計 */} + {/* 背面 */}
- {/* 定義區塊 - 完全復用原樣式 */} + {/* 定義區塊 */}

定義

{card.definition}

- {/* 例句區塊 - 完全復用原樣式 */} + {/* 例句區塊 */}

例句

"{card.example}"

-

"{card.translation}"

+

{card.exampleTranslation}

+ + {/* 同義詞區塊 */} + {card.synonyms && card.synonyms.length > 0 && ( +
+

同義詞

+
+ {card.synonyms.map((synonym, index) => ( + + {synonym} + + ))} +
+
+ )}
- {/* 信心等級評估區 - 完全復用原ConfidenceButtons設計 */} - {isFlipped && ( -
-
-

- 請選擇您對這個詞彙的熟悉程度: -

-
- {[ - { level: 1, label: '完全不懂', color: 'bg-red-100 text-red-700 border-red-200 hover:bg-red-200' }, - { level: 2, label: '模糊', color: 'bg-orange-100 text-orange-700 border-orange-200 hover:bg-orange-200' }, - { level: 3, label: '一般', color: 'bg-yellow-100 text-yellow-700 border-yellow-200 hover:bg-yellow-200' }, - { level: 4, label: '熟悉', color: 'bg-blue-100 text-blue-700 border-blue-200 hover:bg-blue-200' }, - { level: 5, label: '非常熟悉', color: 'bg-green-100 text-green-700 border-green-200 hover:bg-green-200' } - ].map(({ level, label, color }) => { - const isSelected = selectedConfidence === level - return ( - - ) - })} -
- - {/* 提交按鈕 - 選擇後顯示 */} - {hasAnswered && ( - - )} + {/* 信心等級評估區 - 復用您的原設計邏輯 */} +
+
+

+ 請選擇您對這個詞彙的熟悉程度: +

+
+ {[ + { level: 1, label: '完全不懂', color: 'bg-red-100 text-red-700 border-red-200 hover:bg-red-200' }, + { level: 2, label: '模糊', color: 'bg-orange-100 text-orange-700 border-orange-200 hover:bg-orange-200' }, + { level: 3, label: '一般', color: 'bg-yellow-100 text-yellow-700 border-yellow-200 hover:bg-yellow-200' }, + { level: 4, label: '熟悉', color: 'bg-blue-100 text-blue-700 border-blue-200 hover:bg-blue-200' }, + { level: 5, label: '非常熟悉', color: 'bg-green-100 text-green-700 border-green-200 hover:bg-green-200' } + ].map(({ level, label, color }) => { + const isSelected = selectedConfidence === level + return ( + + ) + })}
+ + {/* 提交按鈕 - 選擇後顯示 */} + {hasAnswered && ( + + )}
- )} +
) } \ No newline at end of file diff --git a/frontend/app/review-simple/components/api_seeds.json b/frontend/app/review-simple/components/api_seeds.json new file mode 100644 index 0000000..9d17ab8 --- /dev/null +++ b/frontend/app/review-simple/components/api_seeds.json @@ -0,0 +1,78 @@ +{ + "success": true, + "data": { + "flashcards": [ + { + "id": "1b3e2ec5-4729-4972-ae83-72782a624aa8", + "word": "evidence", + "translation": "證據", + "definition": "The available body of facts or information indicating whether a belief or proposition is true or valid.", + "partOfSpeech": "noun", + "pronunciation": "/ˈevɪdəns/", + "example": "There was no evidence of a crime.", + "exampleTranslation": "沒有犯罪的證據。", + "isFavorite": true, + "difficultyLevelNumeric": 4, + "cefr": "B2", + "createdAt": "2025-10-01T12:48:11.850357", + "updatedAt": "2025-10-01T13:37:22.91802", + "hasExampleImage": false, + "primaryImageUrl": null + }, + { + "id": "5b854991-c64b-464f-b69b-f8946a165257", + "word": "warrants", + "translation": "搜索令", + "definition": "A document issued by a legal or government official authorizing the police or some other body to make an arrest, search premises, or carry out some other action relating to the administration of justice.", + "partOfSpeech": "noun", + "pronunciation": "/ˈwɒrənts/", + "example": "The judge issued the warrants.", + "exampleTranslation": "法官簽發了搜索令。", + "isFavorite": false, + "difficultyLevelNumeric": 5, + "cefr": "C1", + "createdAt": "2025-10-01T12:48:10.161318", + "updatedAt": "2025-10-01T12:48:10.161318", + "hasExampleImage": false, + "primaryImageUrl": null + }, + { + "id": "d6f4227f-bdc9-4f13-a532-aa47f802cf8d", + "word": "obtained", + "translation": "獲得", + "definition": "To get or acquire something.", + "partOfSpeech": "verb", + "pronunciation": "/əbˈteɪnd/", + "example": "She obtained a degree in engineering.", + "exampleTranslation": "她獲得了工程學位。", + "isFavorite": false, + "difficultyLevelNumeric": 4, + "cefr": "B2", + "createdAt": "2025-10-01T12:48:07.640078", + "updatedAt": "2025-10-01T12:48:07.640111", + "hasExampleImage": true, + "primaryImageUrl": "/images/examples/d6f4227f-bdc9-4f13-a532-aa47f802cf8d_078eabb9-3630-4461-b9ea-98a677625d22.png" + }, + { + "id": "26e2e99c-124f-4bfe-859e-8819c68e72b8", + "word": "prioritize", + "translation": "優先安排", + "definition": "designate or treat (something) as being more important than other things", + "partOfSpeech": "verb", + "pronunciation": "/praɪˈɒrɪtaɪz/", + "example": "We need to prioritize our tasks.", + "exampleTranslation": "我們需要優先安排我們的任務。", + "isFavorite": true, + "difficultyLevelNumeric": 4, + "cefr": "B2", + "createdAt": "2025-09-30T18:02:36.316465", + "updatedAt": "2025-10-01T15:49:08.525139", + "hasExampleImage": true, + "primaryImageUrl": "/images/examples/26e2e99c-124f-4bfe-859e-8819c68e72b8_a7923c26-fefd-4705-9921-dc81f44e47c0.png" + } + ], + "count": 4 + }, + "message": null, + "timestamp": "2025-10-03T18:57:25.802194Z" +} \ No newline at end of file diff --git a/frontend/app/review-simple/data.ts b/frontend/app/review-simple/data.ts index 1d73f37..ca5bc89 100644 --- a/frontend/app/review-simple/data.ts +++ b/frontend/app/review-simple/data.ts @@ -1,61 +1,63 @@ -// 極簡MVP的靜態測試數據 -export interface SimpleCard { - id: number +// 模擬真實API數據結構 +import apiSeeds from './components/api_seeds.json' + +// API響應接口 (匹配真實API結構 + 同義詞擴展) +export interface ApiFlashcard { + id: string word: string - definition: string - example: string translation: string + definition: string + partOfSpeech: string pronunciation: string + example: string + exampleTranslation: string + isFavorite: boolean + difficultyLevelNumeric: number + cefr: string + createdAt: string + updatedAt: string + hasExampleImage: boolean + primaryImageUrl: string | null + // 添加同義詞支持 + synonyms?: string[] } -export const SIMPLE_CARDS: SimpleCard[] = [ - { - id: 1, - word: 'hello', - definition: 'a greeting or expression of welcome', - example: 'Hello, how are you today?', - translation: '你好', - pronunciation: '/həˈloʊ/' - }, - { - id: 2, - word: 'beautiful', - definition: 'pleasing the senses or mind aesthetically', - example: 'She has a beautiful smile.', - translation: '美麗的', - pronunciation: '/ˈbjuːtɪfl/' - }, - { - id: 3, - word: 'important', - definition: 'of great significance or value', - example: 'It is important to study every day.', - translation: '重要的', - pronunciation: '/ɪmˈpɔːrtənt/' - }, - { - id: 4, - word: 'happy', - definition: 'feeling or showing pleasure or contentment', - example: 'I am very happy today.', - translation: '快樂的', - pronunciation: '/ˈhæpi/' - }, - { - id: 5, - word: 'learn', - definition: 'gain knowledge or skill by studying or experience', - example: 'I want to learn English.', - translation: '學習', - pronunciation: '/lɜːrn/' +export interface ApiResponse { + success: boolean + data: { + flashcards: ApiFlashcard[] + count: number } -] + message: string | null + timestamp: string +} -// 信心度等級配置 -export const CONFIDENCE_LEVELS = [ - { level: 1, label: '完全不懂', color: 'bg-red-500', description: '第一次見到這個詞' }, - { level: 2, label: '有點印象', color: 'bg-orange-500', description: '似乎見過但不確定意思' }, - { level: 3, label: '還算熟悉', color: 'bg-yellow-500', description: '知道意思但不太確定' }, - { level: 4, label: '很熟悉', color: 'bg-blue-500', description: '清楚知道意思和用法' }, - { level: 5, label: '完全掌握', color: 'bg-green-500', description: '可以自然使用' } -] as const \ No newline at end of file +// 模擬API響應數據 (直接使用真實API格式) +export const MOCK_API_RESPONSE: ApiResponse = apiSeeds as ApiResponse + +// 為API數據添加同義詞 (模擬完整數據) +const addSynonyms = (flashcards: any[]): ApiFlashcard[] => { + const synonymsMap: Record = { + 'evidence': ['proof', 'testimony', 'documentation'], + 'warrants': ['authorizations', 'permits', 'orders'], + 'obtained': ['acquired', 'gained', 'secured'], + 'prioritize': ['rank', 'organize', 'arrange'] + } + + return flashcards.map(card => ({ + ...card, + synonyms: synonymsMap[card.word] || [] + })) +} + +// 提取詞卡數據 (方便組件使用) +export const SIMPLE_CARDS = addSynonyms(MOCK_API_RESPONSE.data.flashcards) + +// 模擬API調用函數 (為未來API集成做準備) +export const mockApiCall = async (): Promise => { + // 模擬網路延遲 + await new Promise(resolve => setTimeout(resolve, 500)) + + // 返回模擬數據 + return MOCK_API_RESPONSE +} \ No newline at end of file