dramaling-vocab-learning/frontend/components/review/ReviewContainer.tsx

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>
)
}