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

220 lines
6.8 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 { useState, useMemo } from 'react'
import { FillTestProps, ReviewCardData } from '@/types/review'
import { useReviewLogic } from '@/hooks/useReviewLogic'
import {
CardHeader,
AudioSection,
ErrorReportButton
} from '@/components/review/shared'
import { getCorrectAnswer } from '@/utils/answerExtractor'
// 優化後的 SentenceFillTest 組件
export const SentenceFillTest: React.FC<FillTestProps> = ({
cardData,
onAnswer,
onReportError,
disabled = false
}) => {
// 使用共用邏輯 Hook
const {
userAnswer,
feedback,
isSubmitted,
setUserAnswer,
submitAnswer
} = useReviewLogic({
cardData,
testType: 'SentenceFillTest'
})
// 填空測試特定狀態
const [inputValue, setInputValue] = useState('')
// 獲取正確答案
const correctAnswer = useMemo(() => {
return getCorrectAnswer(cardData.filledQuestionText || cardData.example, cardData.word)
}, [cardData.filledQuestionText, cardData.example, cardData.word])
// 處理答案提交
const handleSubmit = () => {
if (isSubmitted || disabled || !inputValue.trim()) return
const result = submitAnswer(inputValue.trim())
onAnswer(inputValue.trim())
}
// 處理 Enter 鍵提交
const handleKeyPress = (e: React.KeyboardEvent) => {
if (e.key === 'Enter') {
handleSubmit()
}
}
return (
<div className="max-w-4xl mx-auto">
{/* 詞卡標題 */}
<CardHeader
cardData={cardData}
showTranslation={false}
className="mb-8"
/>
<div className="bg-white rounded-xl shadow-lg p-8">
{/* 標題區 */}
<div className="flex justify-between items-start mb-6">
<h2 className="text-2xl font-bold text-gray-900"></h2>
</div>
{/* 填空句子顯示 */}
<div className="bg-blue-50 rounded-lg p-6 mb-6">
<h3 className="font-semibold text-gray-900 mb-3"></h3>
<div className="text-lg text-gray-800 leading-relaxed mb-4">
{(cardData.filledQuestionText || cardData.example).split('____').map((part, index, array) => (
<span key={index}>
{part}
{index < array.length - 1 && (
<span className="inline-block min-w-24 border-b-2 border-blue-400 mx-1 text-center">
<span className="text-blue-600 font-medium">____</span>
</span>
)}
</span>
))}
</div>
</div>
{/* 答案輸入區 */}
<div className="mb-6">
<label className="block text-sm font-medium text-gray-700 mb-2">
</label>
<input
type="text"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
onKeyPress={handleKeyPress}
disabled={disabled || isSubmitted}
placeholder="請輸入答案..."
className={`
w-full px-4 py-3 rounded-lg border-2 text-lg
${isSubmitted
? feedback?.isCorrect
? 'border-green-500 bg-green-50'
: 'border-red-500 bg-red-50'
: 'border-gray-300 focus:border-blue-500 focus:outline-none'
}
${disabled ? 'opacity-50 cursor-not-allowed' : ''}
`}
/>
</div>
{/* 提交按鈕 */}
{!isSubmitted && (
<div className="flex justify-center mb-6">
<button
onClick={handleSubmit}
disabled={disabled || !inputValue.trim() || isSubmitted}
className={`
px-8 py-3 rounded-lg font-medium text-white
${!inputValue.trim() || disabled
? 'bg-gray-400 cursor-not-allowed'
: 'bg-blue-600 hover:bg-blue-700 active:scale-95'
}
transition-all duration-200
`}
>
</button>
</div>
)}
{/* 結果回饋 */}
{feedback && (
<div className={`p-4 rounded-lg mb-6 ${
feedback.isCorrect ? 'bg-green-50 border border-green-200' : 'bg-red-50 border border-red-200'
}`}>
<p className={`font-medium ${
feedback.isCorrect ? 'text-green-800' : 'text-red-800'
}`}>
{feedback.explanation}
</p>
</div>
)}
{/* 翻譯和音頻 */}
<div className="space-y-4">
<div className="bg-gray-50 rounded-lg p-4">
<h3 className="font-semibold text-gray-900 mb-2"></h3>
<p className="text-gray-700">{cardData.exampleTranslation}</p>
</div>
<AudioSection
word={cardData.word}
pronunciation={cardData.pronunciation}
className="justify-center"
/>
</div>
{/* 例句圖片 */}
{cardData.exampleImage && (
<div className="mt-6 text-center">
<img
src={cardData.exampleImage}
alt={`Example for ${cardData.word}`}
className="max-w-full h-auto rounded-lg shadow-md mx-auto"
style={{ maxHeight: '300px' }}
/>
</div>
)}
{/* 底部按鈕 */}
<div className="flex justify-center mt-6">
<ErrorReportButton
onClick={onReportError}
disabled={disabled}
/>
</div>
</div>
</div>
)
}
// 向後相容包裝器
interface LegacySentenceFillTestProps {
word: string
definition: string
example: string
filledQuestionText?: string
exampleTranslation: string
pronunciation?: string
difficultyLevel: string
exampleImage?: string
onAnswer: (answer: string) => void
onReportError: () => void
onImageClick?: (image: string) => void
disabled?: boolean
}
export const SentenceFillTestLegacy: React.FC<LegacySentenceFillTestProps> = (props) => {
const cardData: ReviewCardData = {
id: `temp_${props.word}`,
word: props.word,
definition: props.definition,
example: props.example,
translation: props.exampleTranslation || '',
pronunciation: props.pronunciation,
synonyms: [],
difficultyLevel: props.difficultyLevel,
exampleTranslation: props.exampleTranslation,
filledQuestionText: props.filledQuestionText,
exampleImage: props.exampleImage
}
return (
<SentenceFillTest
cardData={cardData}
onAnswer={props.onAnswer}
onReportError={props.onReportError}
disabled={props.disabled}
/>
)
}