dramaling-vocab-learning/frontend/components/learn/tests/FlipMemoryTest.tsx

209 lines
7.6 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 } from 'react'
import AudioPlayer from '@/components/AudioPlayer'
interface FlipMemoryTestProps {
word: string
definition: string
example: string
exampleTranslation: string
pronunciation?: string
synonyms?: string[]
difficultyLevel: string
onConfidenceSubmit: (level: number) => void
onReportError: () => void
disabled?: boolean
}
export const FlipMemoryTest: React.FC<FlipMemoryTestProps> = ({
word,
definition,
example,
exampleTranslation,
pronunciation,
synonyms = [],
difficultyLevel,
onConfidenceSubmit,
onReportError,
disabled = false
}) => {
const [isFlipped, setIsFlipped] = useState(false)
const [selectedConfidence, setSelectedConfidence] = useState<number | null>(null)
const handleFlip = () => {
if (!disabled) setIsFlipped(!isFlipped)
}
const handleConfidenceSelect = (level: number) => {
if (disabled) return
setSelectedConfidence(level)
onConfidenceSubmit(level)
}
const confidenceLabels = {
1: '完全不懂',
2: '模糊',
3: '一般',
4: '熟悉',
5: '非常熟悉'
}
return (
<div className="relative">
{/* 錯誤回報按鈕 */}
<div className="flex justify-end mb-2">
<button
onClick={onReportError}
className="px-3 py-2 rounded-md transition-colors text-gray-600 hover:text-gray-900"
>
🚩
</button>
</div>
{/* 翻卡容器 */}
<div
className={`card-container ${disabled ? 'pointer-events-none opacity-75' : 'cursor-pointer'}`}
onClick={handleFlip}
style={{ perspective: '1000px', minHeight: '400px' }}
>
<div
className={`card transition-transform duration-600 ${isFlipped ? 'rotate-y-180' : ''}`}
style={{ transformStyle: 'preserve-3d' }}
>
{/* 正面 */}
<div
className="card-face card-front absolute w-full h-full"
style={{ backfaceVisibility: 'hidden' }}
>
<div className="bg-white rounded-xl shadow-lg p-8 h-full hover:shadow-xl transition-shadow">
<div className="flex justify-between items-start mb-6">
<h2 className="text-2xl font-bold text-gray-900"></h2>
<span className="bg-blue-100 text-blue-800 text-xs px-2 py-1 rounded-full">
{difficultyLevel}
</span>
</div>
<div className="space-y-4">
<p className="text-lg text-gray-700 mb-6 text-left">
</p>
<div className="flex-1 flex items-center justify-center mt-6">
<div className="bg-gray-50 rounded-lg p-8 w-full text-center">
<h3 className="text-4xl font-bold text-gray-900 mb-6">{word}</h3>
<div className="flex items-center justify-center gap-3">
{pronunciation && (
<span className="text-lg text-gray-500">{pronunciation}</span>
)}
<AudioPlayer text={word} />
</div>
</div>
</div>
</div>
</div>
</div>
{/* 背面 */}
<div
className="card-face card-back absolute w-full h-full"
style={{ backfaceVisibility: 'hidden', transform: 'rotateY(180deg)' }}
>
<div className="bg-white rounded-xl shadow-lg p-8 h-full">
<div className="flex justify-between items-start mb-6">
<h2 className="text-2xl font-bold text-gray-900"></h2>
<span className="bg-blue-100 text-blue-800 text-xs px-2 py-1 rounded-full">
{difficultyLevel}
</span>
</div>
<div className="space-y-4">
{/* 定義區塊 */}
<div className="bg-gray-50 rounded-lg p-4">
<h3 className="font-semibold text-gray-900 mb-2 text-left"></h3>
<p className="text-gray-700 text-left">{definition}</p>
</div>
{/* 例句區塊 */}
<div className="bg-gray-50 rounded-lg p-4">
<h3 className="font-semibold text-gray-900 mb-2 text-left"></h3>
<div className="relative">
<p className="text-gray-700 italic mb-2 text-left pr-12">"{example}"</p>
<div className="absolute bottom-0 right-0">
<AudioPlayer text={example} />
</div>
</div>
<p className="text-gray-600 text-sm text-left">"{exampleTranslation}"</p>
</div>
{/* 同義詞區塊 */}
{synonyms.length > 0 && (
<div className="bg-gray-50 rounded-lg p-4">
<h3 className="font-semibold text-gray-900 mb-2 text-left"></h3>
<div className="flex flex-wrap gap-2">
{synonyms.map((synonym, index) => (
<span
key={index}
className="bg-white text-gray-700 px-3 py-1 rounded-full text-sm border border-gray-200"
>
{synonym}
</span>
))}
</div>
</div>
)}
{/* 信心等級評估區 */}
<div className="bg-blue-50 rounded-lg p-4">
<h3 className="font-semibold text-blue-900 mb-3 text-left"></h3>
<div className="grid grid-cols-5 gap-2">
{[1, 2, 3, 4, 5].map(level => (
<button
key={level}
onClick={() => handleConfidenceSelect(level)}
disabled={disabled || selectedConfidence !== null}
className={`py-3 px-4 bg-white border border-blue-200 text-blue-700 rounded-lg transition-all text-center ${
selectedConfidence === level
? 'bg-blue-500 text-white border-blue-500'
: 'hover:bg-blue-100 hover:border-blue-300'
} ${disabled || selectedConfidence !== null ? 'opacity-50 cursor-not-allowed' : ''}`}
>
<div className="font-bold text-lg">{level}</div>
<div className="text-xs">
{confidenceLabels[level as keyof typeof confidenceLabels]}
</div>
</button>
))}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<style jsx>{`
.card-container {
perspective: 1000px;
transition: height 0.3s ease;
}
.card {
position: relative;
width: 100%;
height: 100%;
transform-style: preserve-3d;
}
.rotate-y-180 {
transform: rotateY(180deg);
}
.card-face {
position: absolute;
width: 100%;
height: 100%;
backface-visibility: hidden;
}
`}</style>
</div>
)
}