157 lines
6.3 KiB
TypeScript
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>
|
|
)
|
|
} |