-
翻卡記憶
-
- {difficultyLevel}
-
+
{/* 定義區塊 */}
定義
-
{definition}
+
{cardData.definition}
{/* 例句區塊 */}
例句
-
{example}
+
{cardData.example}
e.stopPropagation()}>
-
+
-
{exampleTranslation}
+
{cardData.exampleTranslation}
{/* 同義詞區塊 */}
- {synonyms.length > 0 && (
+ {cardData.synonyms && cardData.synonyms.length > 0 && (
同義詞
- {synonyms.map((synonym, index) => (
+ {cardData.synonyms.map((synonym, index) => (
= ({
- {/* 信心等級評估區 - 裸露在背景上 */}
- {(
-
-
- {[1, 2, 3, 4, 5].map(level => (
-
handleConfidenceSelect(level)}
- disabled={disabled || selectedConfidence !== null}
- className={`py-4 px-3 border-2 rounded-lg transition-all text-center font-medium ${
- selectedConfidence === level
- ? 'bg-blue-500 text-white border-blue-500 shadow-lg'
- : 'bg-white border-gray-300 text-gray-700 hover:bg-blue-50 hover:border-blue-400 shadow-sm'
- } ${disabled || selectedConfidence !== null ? 'opacity-50 cursor-not-allowed' : 'hover:shadow-md'}`}
- >
-
- {confidenceLabels[level as keyof typeof confidenceLabels]}
-
-
- ))}
-
-
- )}
+ {/* 信心等級評估區 */}
+
+
+
)
-}
\ No newline at end of file
+}
+
+export const FlipMemoryTest = memo(FlipMemoryTestComponent)
+FlipMemoryTest.displayName = 'FlipMemoryTest'
\ No newline at end of file
diff --git a/frontend/components/review/review-tests/SentenceFillTest.tsx b/frontend/components/review/review-tests/SentenceFillTest.tsx
index 8792727..884f019 100644
--- a/frontend/components/review/review-tests/SentenceFillTest.tsx
+++ b/frontend/components/review/review-tests/SentenceFillTest.tsx
@@ -1,152 +1,108 @@
-import { useState, useMemo } from 'react'
-import AudioPlayer from '@/components/AudioPlayer'
+import React, { useState, useMemo, useCallback, memo } from 'react'
import { getCorrectAnswer } from '@/utils/answerExtractor'
-import { ErrorReportButton } from '@/components/review/shared'
+import {
+ ErrorReportButton,
+ SentenceInput,
+ TestResultDisplay,
+ HintPanel
+} from '@/components/review/shared'
+import { FillTestProps } from '@/types/review'
-interface SentenceFillTestProps {
- word: string
- definition: string
- example: string
- filledQuestionText?: string
- exampleTranslation: string
- pronunciation?: string
- synonyms?: string[]
- difficultyLevel: string
- exampleImage?: string
- onAnswer: (answer: string) => void
- onReportError: () => void
- onImageClick?: (image: string) => void
- disabled?: boolean
+interface SentenceFillTestProps extends FillTestProps {
+ // SentenceFillTest specific props (if any)
}
-export const SentenceFillTest: React.FC
= ({
- word,
- definition,
- example,
- filledQuestionText,
- exampleTranslation,
- pronunciation,
- synonyms = [],
- difficultyLevel,
- exampleImage,
+const SentenceFillTestComponent: React.FC = ({
+ cardData,
onAnswer,
onReportError,
- onImageClick,
disabled = false
}) => {
const [fillAnswer, setFillAnswer] = useState('')
const [showResult, setShowResult] = useState(false)
const [showHint, setShowHint] = useState(false)
- const handleSubmit = () => {
+ const handleSubmit = useCallback(() => {
if (disabled || showResult || !fillAnswer.trim()) return
setShowResult(true)
onAnswer(fillAnswer)
- }
+ }, [disabled, showResult, fillAnswer, onAnswer])
- const handleKeyDown = (e: React.KeyboardEvent) => {
- if (e.key === 'Enter' && !showResult && fillAnswer.trim()) {
- handleSubmit()
- }
- }
+ const handleToggleHint = useCallback(() => {
+ setShowHint(prev => !prev)
+ }, [])
- // 🆕 動態計算正確答案:從例句和挖空題目推導
+ // 動態計算正確答案:從例句和挖空題目推導
const correctAnswer = useMemo(() => {
- return getCorrectAnswer(example, filledQuestionText, word);
- }, [example, filledQuestionText, word]);
+ return getCorrectAnswer(cardData.example, cardData.filledQuestionText, cardData.word)
+ }, [cardData.example, cardData.filledQuestionText, cardData.word])
- const isCorrect = fillAnswer.toLowerCase().trim() === correctAnswer.toLowerCase().trim()
- const targetWordLength = correctAnswer.length
- const inputWidth = Math.max(100, Math.max(targetWordLength * 12, fillAnswer.length * 12 + 20))
+ const isCorrect = useMemo(() => {
+ return fillAnswer.toLowerCase().trim() === correctAnswer.toLowerCase().trim()
+ }, [fillAnswer, correctAnswer])
- // 🆕 智能填空渲染:優先使用後端提供的挖空題目
- const renderFilledSentence = () => {
- if (!filledQuestionText) {
- // 降級處理:使用原有的前端挖空邏輯
- return renderSentenceWithInput();
+ // 統一的填空句子渲染邏輯
+ const renderFilledSentence = useCallback(() => {
+ const text = cardData.filledQuestionText || cardData.example
+ const isUsingFilledText = !!cardData.filledQuestionText
+
+ if (isUsingFilledText) {
+ // 使用後端提供的挖空題目
+ const parts = text.split('____')
+ return (
+
+ {parts.map((part, index) => (
+
+ {part}
+ {index < parts.length - 1 && (
+
+ )}
+
+ ))}
+
+ )
+ } else {
+ // 降級處理:使用前端挖空邏輯
+ const parts = text.split(new RegExp(`\\b${cardData.word}\\b`, 'gi'))
+ const matches = text.match(new RegExp(`\\b${cardData.word}\\b`, 'gi')) || []
+
+ return (
+
+ {parts.map((part, index) => (
+
+ {part}
+ {index < matches.length && (
+
+ )}
+
+ ))}
+
+ )
}
-
- // 使用後端提供的挖空題目
- const parts = filledQuestionText.split('____');
-
- return (
-
- {parts.map((part, index) => (
-
- {part}
- {index < parts.length - 1 && (
-
- setFillAnswer(e.target.value)}
- onKeyDown={handleKeyDown}
- placeholder=""
- disabled={disabled || showResult}
- className={`inline-block px-2 py-1 text-center bg-transparent focus:outline-none disabled:bg-gray-100 ${
- fillAnswer
- ? 'border-b-2 border-blue-500'
- : 'border-b-2 border-dashed border-gray-400 focus:border-blue-400 focus:border-solid'
- }`}
- style={{ width: `${inputWidth}px` }}
- />
- {!fillAnswer && (
-
- ____
-
- )}
-
- )}
-
- ))}
-
- );
- }
-
- // 將例句中的目標詞替換為輸入框
- const renderSentenceWithInput = () => {
- const parts = example.split(new RegExp(`\\b${word}\\b`, 'gi'))
- const matches = example.match(new RegExp(`\\b${word}\\b`, 'gi')) || []
-
- return (
-
- {parts.map((part, index) => (
-
- {part}
- {index < matches.length && (
-
- setFillAnswer(e.target.value)}
- onKeyDown={handleKeyDown}
- placeholder=""
- disabled={disabled || showResult}
- className={`inline-block px-2 py-1 text-center bg-transparent focus:outline-none disabled:bg-gray-100 ${
- fillAnswer
- ? 'border-b-2 border-blue-500'
- : 'border-b-2 border-dashed border-gray-400 focus:border-blue-400 focus:border-solid'
- }`}
- style={{ width: `${inputWidth}px` }}
- />
- {!fillAnswer && (
-
- ____
-
- )}
-
- )}
-
- ))}
-
- )
- }
+ }, [
+ cardData.filledQuestionText,
+ cardData.example,
+ cardData.word,
+ fillAnswer,
+ handleSubmit,
+ disabled,
+ showResult,
+ correctAnswer.length
+ ])
return (
@@ -159,19 +115,22 @@ export const SentenceFillTest: React.FC
= ({
例句填空
- {difficultyLevel}
+ {cardData.difficultyLevel}
{/* 圖片區(如果有) */}
- {exampleImage && (
+ {cardData.exampleImage && (
onImageClick?.(exampleImage)}
+ onClick={() => {
+ // 這裡需要處理圖片點擊,但我們暫時移除 onImageClick
+ // 因為新的 cardData 接口可能不包含這個功能
+ }}
/>
@@ -203,7 +162,7 @@ export const SentenceFillTest: React.FC = ({
{!fillAnswer.trim() ? '請先輸入答案' : showResult ? '已確認' : '確認答案'}
setShowHint(!showHint)}
+ onClick={handleToggleHint}
className="px-6 py-2 bg-gray-500 text-white rounded-lg hover:bg-gray-600 transition-colors"
>
{showHint ? '隱藏提示' : '顯示提示'}
@@ -211,73 +170,27 @@ export const SentenceFillTest: React.FC = ({
{/* 提示區域 */}
- {showHint && (
-
-
詞彙定義:
-
{definition}
-
- {/* 同義詞顯示 */}
- {synonyms && synonyms.length > 0 && (
-
-
同義詞提示:
-
- {synonyms.map((synonym, index) => (
-
- {synonym}
-
- ))}
-
-
- )}
-
- )}
+
{/* 結果反饋區 */}
- {showResult && (
-
-
- {isCorrect ? '正確!' : '錯誤!'}
-
-
- {!isCorrect && (
-
-
- 正確答案是:{correctAnswer}
-
-
- )}
-
-
-
-
- {word && {word} }
- {pronunciation && {pronunciation} }
-
-
-
-
-
-
- {example}
-
-
-
- {exampleTranslation}
-
-
-
-
- )}
+