dramaling-vocab-learning/frontend/components/review/review-tests/SentenceFillTest.tsx

206 lines
6.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useState, useMemo, useCallback, memo } from 'react'
import { getCorrectAnswer } from '@/utils/answerExtractor'
import {
TestResultDisplay,
HintPanel,
FillTestContainer,
TextInput
} from '@/components/review/shared'
import { FillTestProps } from '@/types/review'
interface SentenceFillTestProps extends FillTestProps {}
const SentenceFillTestComponent: React.FC<SentenceFillTestProps> = ({
cardData,
onAnswer,
onReportError,
disabled = false
}) => {
const [fillAnswer, setFillAnswer] = useState('')
const [showResult, setShowResult] = useState(false)
const [showHint, setShowHint] = useState(false)
const handleSubmit = useCallback((answer: string) => {
if (disabled || showResult) return
setShowResult(true)
onAnswer(answer)
}, [disabled, showResult, onAnswer])
const handleToggleHint = useCallback(() => {
setShowHint(prev => !prev)
}, [])
// 動態計算正確答案:從例句和挖空題目推導
const correctAnswer = useMemo(() => {
return getCorrectAnswer(cardData.example, cardData.filledQuestionText, cardData.word)
}, [cardData.example, cardData.filledQuestionText, cardData.word])
const isCorrect = useMemo(() => {
return fillAnswer.toLowerCase().trim() === correctAnswer.toLowerCase().trim()
}, [fillAnswer, correctAnswer])
// 統一的填空句子渲染邏輯
const renderFilledSentence = useCallback(() => {
const text = cardData.filledQuestionText || cardData.example
const isUsingFilledText = !!cardData.filledQuestionText
if (isUsingFilledText) {
// 使用後端提供的挖空題目
const parts = text.split('____')
return (
<div className="text-lg text-gray-700 leading-relaxed">
{parts.map((part, index) => (
<span key={index}>
{part}
{index < parts.length - 1 && (
<span className="inline-block mx-1">
<TextInput
value={fillAnswer}
onChange={setFillAnswer}
onSubmit={handleSubmit}
disabled={disabled}
showResult={showResult}
isCorrect={isCorrect}
correctAnswer={correctAnswer}
placeholder=""
/>
</span>
)}
</span>
))}
</div>
)
} else {
// 降級處理:使用前端挖空邏輯
const parts = text.split(new RegExp(`\\b${cardData.word}\\b`, 'gi'))
const matches = text.match(new RegExp(`\\b${cardData.word}\\b`, 'gi')) || []
return (
<div className="text-lg text-gray-700 leading-relaxed">
{parts.map((part, index) => (
<span key={index}>
{part}
{index < matches.length && (
<span className="inline-block mx-1">
<TextInput
value={fillAnswer}
onChange={setFillAnswer}
onSubmit={handleSubmit}
disabled={disabled}
showResult={showResult}
isCorrect={isCorrect}
correctAnswer={correctAnswer}
placeholder=""
/>
</span>
)}
</span>
))}
</div>
)
}
}, [
cardData.filledQuestionText,
cardData.example,
cardData.word,
fillAnswer,
handleSubmit,
disabled,
showResult,
isCorrect,
correctAnswer
])
// 句子顯示區域
const sentenceArea = (
<div className="space-y-6">
{/* 圖片區(如果有) */}
{cardData.exampleImage && (
<div className="bg-gray-50 rounded-lg p-4">
<img
src={cardData.exampleImage}
alt="Example illustration"
className="w-full max-w-md mx-auto rounded-lg cursor-pointer"
onClick={() => {
// 圖片點擊處理 - 後續可以添加放大功能
}}
/>
</div>
)}
{/* 指示文字 */}
<p className="text-lg text-gray-700 text-left">
</p>
{/* 填空句子區域 */}
<div className="bg-gray-50 rounded-lg p-6">
{renderFilledSentence()}
</div>
</div>
)
// 輸入區域(包含操作按鈕和提示)
const inputArea = (
<div className="space-y-4">
{/* 操作按鈕 */}
<div className="flex gap-3">
<button
onClick={() => handleSubmit(fillAnswer)}
disabled={!fillAnswer.trim() || showResult}
className={`px-6 py-2 rounded-lg transition-colors ${
!fillAnswer.trim() || showResult
? 'bg-gray-300 text-gray-500 cursor-not-allowed'
: 'bg-blue-600 text-white hover:bg-blue-700'
}`}
>
{!fillAnswer.trim() ? '請先輸入答案' : showResult ? '已確認' : '確認答案'}
</button>
<button
onClick={handleToggleHint}
className="px-6 py-2 bg-gray-500 text-white rounded-lg hover:bg-gray-600 transition-colors"
>
{showHint ? '隱藏提示' : '顯示提示'}
</button>
</div>
{/* 提示區域 */}
<HintPanel
isVisible={showHint}
definition={cardData.definition}
synonyms={cardData.synonyms}
/>
</div>
)
// 結果顯示區域
const resultArea = showResult ? (
<TestResultDisplay
isCorrect={isCorrect}
correctAnswer={correctAnswer}
userAnswer={fillAnswer}
word={cardData.word}
pronunciation={cardData.pronunciation}
example={cardData.example}
exampleTranslation={cardData.exampleTranslation}
showResult={showResult}
/>
) : null
return (
<FillTestContainer
cardData={cardData}
testTitle="例句填空"
sentenceArea={sentenceArea}
inputArea={inputArea}
resultArea={resultArea}
onAnswer={onAnswer}
onReportError={onReportError}
disabled={disabled}
/>
)
}
export const SentenceFillTest = memo(SentenceFillTestComponent)
SentenceFillTest.displayName = 'SentenceFillTest'