284 lines
9.3 KiB
TypeScript
284 lines
9.3 KiB
TypeScript
import { useRef } from 'react'
|
|
|
|
// 複習模式類型
|
|
type ReviewMode = 'flip-memory' | 'vocab-choice' | 'vocab-listening' | 'sentence-listening' | 'sentence-fill' | 'sentence-reorder' | 'sentence-speaking'
|
|
|
|
// 擴展的Flashcard接口
|
|
interface ExtendedFlashcard {
|
|
id: string
|
|
word: string
|
|
definition: string
|
|
example: string
|
|
difficultyLevel?: string
|
|
[key: string]: any
|
|
}
|
|
|
|
interface ReviewContainerProps {
|
|
// 當前詞卡和模式
|
|
currentCard: ExtendedFlashcard | null
|
|
mode: ReviewMode
|
|
|
|
// 答題狀態
|
|
selectedAnswer: string | null
|
|
showResult: boolean
|
|
fillAnswer: string
|
|
showHint: boolean
|
|
isFlipped: boolean
|
|
|
|
// 題型特定狀態
|
|
quizOptions: string[]
|
|
shuffledWords: string[]
|
|
arrangedWords: string[]
|
|
reorderResult: boolean | null
|
|
|
|
// 導航狀態
|
|
currentCardIndex: number
|
|
totalCards: number
|
|
|
|
// 事件處理器
|
|
onAnswer: (answer: string) => void
|
|
onFillSubmit: () => void
|
|
onFillAnswerChange: (answer: string) => void
|
|
onToggleHint: () => void
|
|
onFlip: () => void
|
|
onConfidenceLevel: (level: number) => void
|
|
onWordClick: (word: string) => void
|
|
onRemoveFromArranged: (word: string) => void
|
|
onCheckReorderAnswer: () => void
|
|
onResetReorder: () => void
|
|
onReportError: () => void
|
|
onNavigate: (direction: 'previous' | 'next') => void
|
|
setModalImage: (image: string | null) => void
|
|
}
|
|
|
|
export const ReviewContainer: React.FC<ReviewContainerProps> = ({
|
|
currentCard,
|
|
mode,
|
|
selectedAnswer,
|
|
showResult,
|
|
fillAnswer,
|
|
showHint,
|
|
isFlipped,
|
|
quizOptions,
|
|
shuffledWords,
|
|
arrangedWords,
|
|
reorderResult,
|
|
currentCardIndex,
|
|
totalCards,
|
|
onAnswer,
|
|
onFillSubmit,
|
|
onFillAnswerChange,
|
|
onToggleHint,
|
|
onFlip,
|
|
onConfidenceLevel,
|
|
onWordClick,
|
|
onRemoveFromArranged,
|
|
onCheckReorderAnswer,
|
|
onResetReorder,
|
|
onReportError,
|
|
onNavigate,
|
|
setModalImage
|
|
}) => {
|
|
// Refs for card height calculation
|
|
const cardContainerRef = useRef<HTMLDivElement>(null)
|
|
const cardFrontRef = useRef<HTMLDivElement>(null)
|
|
const cardBackRef = useRef<HTMLDivElement>(null)
|
|
|
|
if (!currentCard) {
|
|
return (
|
|
<div className="flex items-center justify-center min-h-[400px]">
|
|
<div className="text-gray-500 text-lg">載入詞卡中...</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
// 渲染不同的測驗類型
|
|
const renderTestComponent = () => {
|
|
switch (mode) {
|
|
case 'flip-memory':
|
|
return (
|
|
<div className="text-center py-8 bg-white rounded-xl shadow-lg p-6">
|
|
<h3 className="text-xl font-bold mb-4">翻卡記憶測驗</h3>
|
|
<div className="text-lg mb-4">詞卡: {currentCard.word}</div>
|
|
|
|
<div className="mb-6">
|
|
{!isFlipped ? (
|
|
<div className="p-6 bg-blue-50 rounded-lg">
|
|
<div className="text-2xl font-bold">{currentCard.word}</div>
|
|
</div>
|
|
) : (
|
|
<div className="p-6 bg-green-50 rounded-lg">
|
|
<div className="text-lg mb-2">{currentCard.definition}</div>
|
|
<div className="text-sm text-gray-600">{currentCard.example}</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
<button
|
|
onClick={onFlip}
|
|
className="bg-blue-500 text-white px-6 py-3 rounded-lg hover:bg-blue-600 mb-4"
|
|
>
|
|
{isFlipped ? '翻回正面' : '查看答案'}
|
|
</button>
|
|
|
|
{isFlipped && (
|
|
<div className="flex gap-2 justify-center">
|
|
<button onClick={() => onNavigate('next')} className="bg-green-500 text-white px-4 py-2 rounded">繼續</button>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
|
|
case 'vocab-choice':
|
|
return (
|
|
<div className="text-center py-8 bg-white rounded-xl shadow-lg p-6">
|
|
<h3 className="text-xl font-bold mb-4">詞彙選擇測驗</h3>
|
|
<div className="text-lg mb-4">選擇正確的單字意思</div>
|
|
<div className="text-sm text-gray-600 mb-6">{currentCard.definition}</div>
|
|
|
|
<div className="space-y-3 max-w-md mx-auto">
|
|
{quizOptions.map((option, index) => (
|
|
<button
|
|
key={index}
|
|
onClick={() => onAnswer(option)}
|
|
className={`w-full p-3 rounded-lg border ${
|
|
selectedAnswer === option
|
|
? 'bg-blue-100 border-blue-500'
|
|
: 'bg-gray-50 border-gray-200 hover:bg-gray-100'
|
|
}`}
|
|
disabled={showResult}
|
|
>
|
|
{option}
|
|
</button>
|
|
))}
|
|
</div>
|
|
|
|
{showResult && (
|
|
<div className="mt-6">
|
|
<button onClick={() => onNavigate('next')} className="bg-green-500 text-white px-4 py-2 rounded">繼續</button>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
|
|
case 'sentence-fill':
|
|
return (
|
|
<div className="text-center py-8 bg-white rounded-xl shadow-lg p-6">
|
|
<h3 className="text-xl font-bold mb-4">例句填空測驗</h3>
|
|
<div className="text-lg mb-6">填入正確的單字</div>
|
|
|
|
<div className="mb-6">
|
|
<div className="text-lg">
|
|
{currentCard.example?.replace(currentCard.word, '___')}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="mb-4">
|
|
<input
|
|
type="text"
|
|
value={fillAnswer}
|
|
onChange={(e) => onFillAnswerChange(e.target.value)}
|
|
className="border-2 border-gray-300 px-4 py-2 rounded-lg w-48 focus:border-blue-500"
|
|
placeholder="輸入答案"
|
|
/>
|
|
</div>
|
|
|
|
<button
|
|
onClick={onFillSubmit}
|
|
className="bg-blue-500 text-white px-6 py-2 rounded-lg hover:bg-blue-600"
|
|
disabled={!fillAnswer.trim()}
|
|
>
|
|
提交答案
|
|
</button>
|
|
|
|
{showResult && (
|
|
<div className="mt-6">
|
|
<button onClick={() => onNavigate('next')} className="bg-green-500 text-white px-4 py-2 rounded">繼續</button>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
|
|
case 'sentence-reorder':
|
|
return (
|
|
<div className="text-center py-8 bg-white rounded-xl shadow-lg p-6">
|
|
<h3 className="text-xl font-bold mb-4">例句重組測驗</h3>
|
|
<div className="text-lg mb-6">重新排列單字組成正確句子</div>
|
|
|
|
<div className="mb-6">
|
|
<p className="mb-2">可用詞語:</p>
|
|
<div className="flex flex-wrap gap-2 justify-center">
|
|
{shuffledWords.map((word, index) => (
|
|
<button
|
|
key={index}
|
|
onClick={() => onWordClick(word)}
|
|
className="bg-gray-200 hover:bg-gray-300 px-3 py-2 rounded-lg"
|
|
>
|
|
{word}
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="mb-6">
|
|
<p className="mb-2">你的句子:</p>
|
|
<div className="flex flex-wrap gap-2 justify-center min-h-[50px] bg-blue-50 p-3 rounded-lg">
|
|
{arrangedWords.map((word, index) => (
|
|
<button
|
|
key={index}
|
|
onClick={() => onRemoveFromArranged(word)}
|
|
className="bg-blue-200 hover:bg-blue-300 px-3 py-2 rounded-lg"
|
|
>
|
|
{word}
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="space-x-3">
|
|
<button
|
|
onClick={onCheckReorderAnswer}
|
|
className="bg-green-500 text-white px-6 py-2 rounded-lg hover:bg-green-600"
|
|
disabled={arrangedWords.length === 0}
|
|
>
|
|
檢查答案
|
|
</button>
|
|
<button
|
|
onClick={onResetReorder}
|
|
className="bg-gray-500 text-white px-4 py-2 rounded-lg hover:bg-gray-600"
|
|
>
|
|
重置
|
|
</button>
|
|
</div>
|
|
|
|
{reorderResult !== null && (
|
|
<div className="mt-6">
|
|
<div className={`text-lg mb-3 ${reorderResult ? 'text-green-600' : 'text-red-600'}`}>
|
|
{reorderResult ? '✅ 正確!' : '❌ 不正確,請再試試'}
|
|
</div>
|
|
{reorderResult && (
|
|
<button onClick={() => onNavigate('next')} className="bg-green-500 text-white px-4 py-2 rounded">繼續</button>
|
|
)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
|
|
default:
|
|
return (
|
|
<div className="text-center py-8 bg-white rounded-xl shadow-lg p-6">
|
|
<div className="text-gray-500 text-lg">
|
|
測驗類型 "{mode}" 尚未實現
|
|
</div>
|
|
<button onClick={() => onNavigate('next')} className="mt-4 bg-gray-500 text-white px-4 py-2 rounded">跳過</button>
|
|
</div>
|
|
)
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className="review-container">
|
|
{renderTestComponent()}
|
|
</div>
|
|
)
|
|
} |