dramaling-vocab-learning/frontend/components/flashcards/FlashcardCard.tsx

157 lines
6.3 KiB
TypeScript

import React from 'react'
import Link from 'next/link'
import { Flashcard } from '@/lib/services/flashcards'
import { getPartOfSpeechDisplay, getCEFRColor, getMasteryColor, getMasteryText, formatNextReviewDate, getFlashcardImageUrl } from '@/lib/utils/flashcardUtils'
interface FlashcardCardProps {
flashcard: Flashcard
onEdit: () => void
onDelete: () => void
onFavorite: () => void
onImageGenerate: () => void
isGenerating?: boolean
generationProgress?: string
}
export const FlashcardCard: React.FC<FlashcardCardProps> = ({
flashcard,
onEdit,
onDelete,
onFavorite,
onImageGenerate,
isGenerating = false,
generationProgress = ''
}) => {
const exampleImageUrl = getFlashcardImageUrl(flashcard)
return (
<div className="bg-white rounded-xl shadow-sm border border-gray-200 p-6 hover:shadow-md transition-shadow relative">
{/* CEFR標籤 */}
<div className="absolute top-4 right-4">
<span className={`px-3 py-1 rounded-full text-sm font-medium border ${getCEFRColor(flashcard.cefr)}`}>
{flashcard.cefr}
</span>
</div>
{/* 詞彙標題 */}
<div className="mb-4 pr-16">
<h3 className="text-xl font-bold text-gray-900 mb-1">{flashcard.word}</h3>
<div className="flex items-center gap-2 text-sm text-gray-600">
<span className="bg-gray-100 px-2 py-1 rounded">
{getPartOfSpeechDisplay(flashcard.partOfSpeech)}
</span>
<span>{flashcard.pronunciation}</span>
</div>
</div>
{/* 翻譯和定義 */}
<div className="mb-4">
<p className="text-green-700 font-medium mb-2">{flashcard.translation}</p>
<p className="text-gray-600 text-sm leading-relaxed">{flashcard.definition}</p>
</div>
{/* 例句 */}
<div className="mb-4">
<p className="text-blue-700 italic mb-1">"{flashcard.example}"</p>
{flashcard.exampleTranslation && (
<p className="text-blue-600 text-sm">"{flashcard.exampleTranslation}"</p>
)}
</div>
{/* 例句圖片 */}
<div className="mb-4">
{exampleImageUrl ? (
<div className="relative">
<img
src={exampleImageUrl}
alt={`${flashcard.word} example`}
className="w-full h-40 object-cover rounded-lg"
/>
{!isGenerating && (
<button
onClick={onImageGenerate}
className="absolute top-2 right-2 px-2 py-1 text-xs bg-white bg-opacity-90 text-gray-700 rounded-md hover:bg-opacity-100 transition-all shadow-sm"
>
</button>
)}
{isGenerating && (
<div className="absolute inset-0 bg-white bg-opacity-90 rounded-lg flex items-center justify-center">
<div className="text-center">
<div className="animate-spin rounded-full h-6 w-6 border-b-2 border-blue-600 mx-auto mb-2"></div>
<p className="text-xs text-gray-600">{generationProgress}</p>
</div>
</div>
)}
</div>
) : (
<div className="w-full h-40 bg-gray-100 rounded-lg border-2 border-dashed border-gray-300 flex items-center justify-center">
<div className="text-center text-gray-500">
<svg className="w-8 h-8 mx-auto mb-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
<p className="text-sm"></p>
<button
onClick={onImageGenerate}
disabled={isGenerating}
className="mt-2 px-3 py-1 text-sm bg-blue-100 text-blue-700 rounded-full hover:bg-blue-200 transition-colors disabled:opacity-50"
>
{isGenerating ? generationProgress : '生成圖片'}
</button>
</div>
</div>
)}
</div>
{/* 學習統計 */}
<div className="mb-4 grid grid-cols-3 gap-2 text-center">
<div className="bg-gray-50 rounded p-2">
<div className={`text-sm font-medium px-2 py-1 rounded ${getMasteryColor(flashcard.masteryLevel)}`}>
{getMasteryText(flashcard.masteryLevel)}
</div>
<div className="text-xs text-gray-600 mt-1">{flashcard.masteryLevel}%</div>
</div>
<div className="bg-gray-50 rounded p-2">
<div className="text-sm font-medium text-gray-900">{flashcard.timesReviewed}</div>
<div className="text-xs text-gray-600"></div>
</div>
<div className="bg-gray-50 rounded p-2">
<div className="text-sm font-medium text-gray-900">{formatNextReviewDate(flashcard.nextReviewDate)}</div>
<div className="text-xs text-gray-600"></div>
</div>
</div>
{/* 操作按鈕 */}
<div className="flex gap-2">
<Link
href={`/flashcards/${flashcard.id}`}
className="flex-1 bg-blue-600 text-white py-2 px-3 rounded-lg text-sm font-medium hover:bg-blue-700 transition-colors text-center"
>
</Link>
<button
onClick={onFavorite}
className={`px-3 py-2 rounded-lg text-sm transition-colors ${
flashcard.isFavorite
? 'bg-yellow-100 text-yellow-700 hover:bg-yellow-200'
: 'bg-gray-100 text-gray-600 hover:bg-yellow-50'
}`}
>
{flashcard.isFavorite ? '★' : '☆'}
</button>
<button
onClick={onEdit}
className="px-3 py-2 bg-gray-100 text-gray-600 rounded-lg text-sm hover:bg-gray-200 transition-colors"
>
</button>
<button
onClick={onDelete}
className="px-3 py-2 bg-red-100 text-red-600 rounded-lg text-sm hover:bg-red-200 transition-colors"
>
</button>
</div>
</div>
)
}