183 lines
6.7 KiB
TypeScript
183 lines
6.7 KiB
TypeScript
'use client'
|
||
|
||
import { useState, useCallback } from 'react'
|
||
import { CardState } from '@/lib/data/reviewSimpleData'
|
||
import { QuizHeader } from '../ui/QuizHeader'
|
||
import { BluePlayButton } from '@/components/shared/BluePlayButton'
|
||
|
||
|
||
interface VocabChoiceTestProps {
|
||
card: CardState
|
||
options: string[]
|
||
onAnswer: (confidence: number) => void
|
||
onSkip: () => void
|
||
}
|
||
|
||
export function VocabChoiceQuiz({ card, options, onAnswer, onSkip }: VocabChoiceTestProps) {
|
||
const [selectedAnswer, setSelectedAnswer] = useState<string | null>(null)
|
||
const [showResult, setShowResult] = useState(false)
|
||
const [hasAnswered, setHasAnswered] = useState(false)
|
||
|
||
const handleAnswerSelect = useCallback((answer: string) => {
|
||
if (showResult || hasAnswered) return
|
||
|
||
setSelectedAnswer(answer)
|
||
setShowResult(true)
|
||
setHasAnswered(true)
|
||
}, [showResult, hasAnswered])
|
||
|
||
const handleSkipClick = useCallback(() => {
|
||
onSkip()
|
||
}, [onSkip])
|
||
|
||
const handleNext = useCallback(() => {
|
||
if (!hasAnswered || !selectedAnswer) return
|
||
|
||
// 判斷答案是否正確,正確給2分,錯誤給0分
|
||
const isCorrect = selectedAnswer === card.word
|
||
const confidence = isCorrect ? 2 : 0
|
||
|
||
onAnswer(confidence)
|
||
|
||
// 重置狀態為下一題準備
|
||
setSelectedAnswer(null)
|
||
setShowResult(false)
|
||
setHasAnswered(false)
|
||
}, [hasAnswered, selectedAnswer, card.word, onAnswer])
|
||
|
||
const isCorrect = selectedAnswer === card.word
|
||
|
||
return (
|
||
<div>
|
||
<div className="bg-white rounded-xl shadow-lg p-8">
|
||
<QuizHeader
|
||
title="詞彙選擇"
|
||
cefr={card.cefr}
|
||
/>
|
||
|
||
{/* 問題區域 */}
|
||
<div className="mb-8">
|
||
<div className="bg-gray-50 rounded-lg p-6 mb-4">
|
||
<h3 className="font-semibold text-gray-900 mb-3 text-left">定義</h3>
|
||
<p className="text-gray-700 text-left text-lg leading-relaxed">{card.definition}</p>
|
||
</div>
|
||
<p className="text-lg text-gray-700 text-left">
|
||
請選擇符合上述定義的英文詞彙:
|
||
</p>
|
||
</div>
|
||
|
||
{/* 選項區域 */}
|
||
<div className="mb-6">
|
||
<div className="grid grid-cols-2 gap-3">
|
||
{options.map((option, index) => {
|
||
const isSelected = selectedAnswer === option
|
||
const isCorrectOption = option === card.word
|
||
|
||
let buttonClass = 'p-4 rounded-lg border-2 text-center font-medium transition-all duration-200 cursor-pointer active:scale-95'
|
||
|
||
if (showResult) {
|
||
if (isSelected && isCorrectOption) {
|
||
// 選中且正確
|
||
buttonClass += ' bg-green-100 text-green-700 border-green-200 ring-2 ring-green-400'
|
||
} else if (isSelected && !isCorrectOption) {
|
||
// 選中但錯誤
|
||
buttonClass += ' bg-red-100 text-red-700 border-red-200 ring-2 ring-red-400'
|
||
} else if (!isSelected && isCorrectOption) {
|
||
// 未選中但是正確答案
|
||
buttonClass += ' bg-green-50 text-green-600 border-green-200'
|
||
} else {
|
||
// 未選中且非正確答案
|
||
buttonClass += ' bg-gray-50 text-gray-500 border-gray-200'
|
||
}
|
||
} else {
|
||
// 未答題狀態
|
||
buttonClass += ' bg-blue-50 text-blue-700 border-blue-200 hover:bg-blue-100'
|
||
}
|
||
|
||
return (
|
||
<button
|
||
key={index}
|
||
onClick={() => handleAnswerSelect(option)}
|
||
disabled={hasAnswered}
|
||
className={buttonClass}
|
||
>
|
||
{option}
|
||
</button>
|
||
)
|
||
})}
|
||
</div>
|
||
</div>
|
||
|
||
{/* 結果顯示區域 */}
|
||
{showResult && (
|
||
<div className="mb-6">
|
||
<div className={`p-4 rounded-lg ${isCorrect ? 'bg-green-50 border border-green-200' : 'bg-red-50 border border-red-200'}`}>
|
||
<div className="flex items-center mb-3">
|
||
<span className={`text-2xl mr-3 ${isCorrect ? 'text-green-600' : 'text-red-600'}`}>
|
||
{isCorrect ? '✅' : '❌'}
|
||
</span>
|
||
<h3 className={`text-lg font-semibold ${isCorrect ? 'text-green-800' : 'text-red-800'}`}>
|
||
{isCorrect ? '答對了!' : '答錯了'}
|
||
</h3>
|
||
</div>
|
||
|
||
<div className="space-y-2 text-left">
|
||
<p className="text-gray-700">
|
||
<strong>正確答案:</strong> {card.word}
|
||
</p>
|
||
<p className="text-gray-700">
|
||
<strong>發音:</strong> {card.pronunciation}
|
||
<span className='ml-2' onClick={(e) => e.stopPropagation()}>
|
||
<BluePlayButton
|
||
text={card.word}
|
||
size="sm"
|
||
title="播放單詞發音"
|
||
rate={0.8}
|
||
lang="en-US"
|
||
/>
|
||
</span>
|
||
</p>
|
||
<p className="text-gray-700">
|
||
<strong>例句:</strong> "{card.example}"
|
||
<span className='ml-2' onClick={(e) => e.stopPropagation()}>
|
||
<BluePlayButton
|
||
text={card.example}
|
||
size="sm"
|
||
title="播放單詞發音"
|
||
rate={0.8}
|
||
lang="en-US"
|
||
/>
|
||
</span>
|
||
</p>
|
||
<p className="text-gray-600 text-sm">
|
||
{card.exampleTranslation}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
{/* 按鈕區域 - 根據答題狀態顯示不同按鈕 */}
|
||
<div className="mt-6">
|
||
{!hasAnswered ? (
|
||
// 未答題時顯示跳過按鈕
|
||
<button
|
||
onClick={handleSkipClick}
|
||
className="w-full border border-gray-300 text-gray-700 py-3 px-4 rounded-lg font-medium hover:bg-gray-50 transition-colors"
|
||
>
|
||
跳過
|
||
</button>
|
||
) : (
|
||
// 已答題時顯示下一題按鈕
|
||
<button
|
||
onClick={handleNext}
|
||
className="w-full bg-blue-600 text-white py-3 px-4 rounded-lg font-medium hover:bg-blue-700 transition-colors"
|
||
>
|
||
下一題
|
||
</button>
|
||
)}
|
||
</div>
|
||
</div>
|
||
)
|
||
} |