170 lines
5.3 KiB
TypeScript
170 lines
5.3 KiB
TypeScript
import { useState } from 'react'
|
||
import { ChoiceTestProps, ReviewCardData } from '@/types/review'
|
||
import { useReviewLogic } from '@/hooks/useReviewLogic'
|
||
import {
|
||
CardHeader,
|
||
AudioSection,
|
||
ErrorReportButton
|
||
} from '@/components/review/shared'
|
||
|
||
// 優化後的 VocabChoiceTest 組件
|
||
export const VocabChoiceTest: React.FC<ChoiceTestProps> = ({
|
||
cardData,
|
||
options,
|
||
onAnswer,
|
||
onReportError,
|
||
disabled = false
|
||
}) => {
|
||
// 使用共用邏輯 Hook
|
||
const {
|
||
userAnswer,
|
||
feedback,
|
||
isSubmitted,
|
||
submitAnswer
|
||
} = useReviewLogic({
|
||
cardData,
|
||
testType: 'VocabChoiceTest'
|
||
})
|
||
|
||
const [selectedAnswer, setSelectedAnswer] = useState<string | null>(null)
|
||
|
||
// 處理選項點擊
|
||
const handleOptionClick = (option: string) => {
|
||
if (isSubmitted || disabled) return
|
||
|
||
setSelectedAnswer(option)
|
||
const result = submitAnswer(option)
|
||
onAnswer(option)
|
||
}
|
||
|
||
return (
|
||
<div className="max-w-4xl mx-auto">
|
||
{/* 音頻播放區 */}
|
||
<AudioSection
|
||
word={cardData.word}
|
||
pronunciation={cardData.pronunciation}
|
||
className="mb-6"
|
||
/>
|
||
|
||
<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>
|
||
|
||
{/* 指示文字 */}
|
||
<p className="text-lg text-gray-700 mb-6 text-left">
|
||
請選擇符合上述定義的英文詞彙:
|
||
</p>
|
||
|
||
{/* 定義顯示區 */}
|
||
<div className="text-center mb-8">
|
||
<div className="bg-gray-50 rounded-lg p-4 mb-6">
|
||
<h3 className="font-semibold text-gray-900 mb-2 text-left">定義</h3>
|
||
<p className="text-gray-700 text-left">{cardData.definition}</p>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 選項區域 - 響應式網格布局 */}
|
||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3 mb-6">
|
||
{options.map((option, idx) => {
|
||
const isSelected = selectedAnswer === option
|
||
const isCorrect = feedback && feedback.isCorrect && isSelected
|
||
const isWrong = feedback && !feedback.isCorrect && isSelected
|
||
|
||
return (
|
||
<button
|
||
key={idx}
|
||
onClick={() => handleOptionClick(option)}
|
||
disabled={disabled || isSubmitted}
|
||
className={`
|
||
p-4 rounded-lg border-2 transition-all duration-200
|
||
text-left font-medium
|
||
${!isSubmitted
|
||
? 'border-gray-200 hover:border-blue-300 hover:bg-blue-50'
|
||
: isCorrect
|
||
? 'border-green-500 bg-green-50 text-green-700'
|
||
: isWrong
|
||
? 'border-red-500 bg-red-50 text-red-700'
|
||
: option.toLowerCase() === cardData.word.toLowerCase()
|
||
? 'border-green-500 bg-green-50 text-green-700'
|
||
: 'border-gray-200 bg-gray-50 text-gray-500'
|
||
}
|
||
${disabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer'}
|
||
`}
|
||
>
|
||
{option}
|
||
</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="bg-blue-50 rounded-lg p-4 mb-6">
|
||
<h3 className="font-semibold text-gray-900 mb-2">例句</h3>
|
||
<p className="text-gray-800 mb-2">{cardData.example}</p>
|
||
<p className="text-gray-600 text-sm">{cardData.exampleTranslation}</p>
|
||
</div>
|
||
|
||
{/* 底部按鈕 */}
|
||
<div className="flex justify-center">
|
||
<ErrorReportButton
|
||
onClick={onReportError}
|
||
disabled={disabled}
|
||
/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
// 向後相容包裝器
|
||
interface LegacyVocabChoiceTestProps {
|
||
word: string
|
||
definition: string
|
||
example: string
|
||
exampleTranslation: string
|
||
pronunciation?: string
|
||
difficultyLevel: string
|
||
options: string[]
|
||
onAnswer: (answer: string) => void
|
||
onReportError: () => void
|
||
disabled?: boolean
|
||
}
|
||
|
||
export const VocabChoiceTestLegacy: React.FC<LegacyVocabChoiceTestProps> = (props) => {
|
||
const cardData: ReviewCardData = {
|
||
id: `temp_${props.word}`,
|
||
word: props.word,
|
||
definition: props.definition,
|
||
example: props.example,
|
||
translation: props.exampleTranslation || '',
|
||
pronunciation: props.pronunciation,
|
||
synonyms: [], // VocabChoiceTest 原來沒有 synonyms
|
||
difficultyLevel: props.difficultyLevel,
|
||
exampleTranslation: props.exampleTranslation
|
||
}
|
||
|
||
return (
|
||
<VocabChoiceTest
|
||
cardData={cardData}
|
||
options={props.options}
|
||
onAnswer={props.onAnswer}
|
||
onReportError={props.onReportError}
|
||
disabled={props.disabled}
|
||
/>
|
||
)
|
||
} |