dramaling-vocab-learning/app/flashcards/page.tsx

403 lines
19 KiB
TypeScript

'use client'
import { useState } from 'react'
import Link from 'next/link'
export default function FlashcardsPage() {
const [activeTab, setActiveTab] = useState('my-cards')
const [selectedSet, setSelectedSet] = useState<number | null>(null)
const [searchTerm, setSearchTerm] = useState('')
const [filterTag, setFilterTag] = useState('all')
// Mock data
const cardSets = [
{
id: 1,
name: '美劇經典台詞',
description: '從熱門美劇中精選的實用對話',
cardCount: 45,
progress: 60,
lastStudied: '2 小時前',
tags: ['影視', '口語'],
color: 'bg-blue-500'
},
{
id: 2,
name: '商務英文必備',
description: '職場溝通和商業會議常用詞彙',
cardCount: 30,
progress: 30,
lastStudied: '昨天',
tags: ['商務', '正式'],
color: 'bg-purple-500'
},
{
id: 3,
name: '日常對話',
description: '生活中最常用的英文表達',
cardCount: 25,
progress: 80,
lastStudied: '3 天前',
tags: ['日常', '基礎'],
color: 'bg-green-500'
},
{
id: 4,
name: 'TOEFL 核心詞彙',
description: '托福考試高頻詞彙整理',
cardCount: 100,
progress: 15,
lastStudied: '1 週前',
tags: ['考試', '學術'],
color: 'bg-orange-500'
},
{
id: 5,
name: '科技新聞詞彙',
description: '科技領域專業術語和流行用語',
cardCount: 35,
progress: 45,
lastStudied: '5 天前',
tags: ['科技', '專業'],
color: 'bg-indigo-500'
}
]
const flashcards = [
{ id: 1, word: 'negotiate', translation: '協商', setId: 1, mastery: 80, nextReview: '明天' },
{ id: 2, word: 'accomplish', translation: '完成', setId: 1, mastery: 60, nextReview: '今天' },
{ id: 3, word: 'perspective', translation: '觀點', setId: 2, mastery: 90, nextReview: '3天後' },
{ id: 4, word: 'substantial', translation: '大量的', setId: 2, mastery: 40, nextReview: '今天' },
{ id: 5, word: 'implement', translation: '實施', setId: 3, mastery: 70, nextReview: '明天' },
]
const tags = ['all', '影視', '商務', '日常', '考試', '科技', '口語', '正式', '基礎', '學術', '專業']
const filteredSets = cardSets.filter(set =>
set.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
set.description.toLowerCase().includes(searchTerm.toLowerCase())
)
const filteredCards = flashcards.filter(card =>
card.word.toLowerCase().includes(searchTerm.toLowerCase()) ||
card.translation.includes(searchTerm)
)
return (
<div className="min-h-screen bg-gray-50">
{/* Navigation */}
<nav className="bg-white shadow-sm border-b">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex justify-between h-16">
<div className="flex items-center space-x-8">
<Link href="/dashboard" className="text-2xl font-bold text-primary">DramaLing</Link>
<div className="hidden md:flex space-x-6">
<Link href="/dashboard" className="text-gray-600 hover:text-gray-900"></Link>
<Link href="/flashcards" className="text-gray-900 font-medium"></Link>
<Link href="/learn" className="text-gray-600 hover:text-gray-900"></Link>
<Link href="/generate" className="text-gray-600 hover:text-gray-900">AI </Link>
</div>
</div>
<div className="flex items-center space-x-4">
<Link
href="/generate"
className="bg-primary text-white px-4 py-2 rounded-lg hover:bg-primary-hover transition-colors text-sm font-medium"
>
+
</Link>
</div>
</div>
</div>
</nav>
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
{/* Header */}
<div className="mb-8">
<h1 className="text-3xl font-bold text-gray-900 mb-2"></h1>
<p className="text-gray-600"></p>
</div>
{/* Search and Filter */}
<div className="bg-white rounded-lg shadow-sm p-4 mb-6">
<div className="flex flex-col md:flex-row gap-4">
<div className="flex-1">
<input
type="text"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
placeholder="搜尋詞卡或卡組..."
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent outline-none"
/>
</div>
<div className="flex gap-2">
<select
value={filterTag}
onChange={(e) => setFilterTag(e.target.value)}
className="px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent outline-none"
>
{tags.map(tag => (
<option key={tag} value={tag}>
{tag === 'all' ? '所有標籤' : tag}
</option>
))}
</select>
<button className="px-4 py-2 border border-gray-300 rounded-lg hover:bg-gray-50 transition-colors">
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.293A1 1 0 013 6.586V4z" />
</svg>
</button>
</div>
</div>
</div>
{/* Tabs */}
<div className="bg-white rounded-lg shadow-sm mb-6">
<div className="border-b border-gray-200">
<nav className="-mb-px flex space-x-8 px-6" aria-label="Tabs">
<button
onClick={() => setActiveTab('my-cards')}
className={`py-4 px-1 border-b-2 font-medium text-sm ${
activeTab === 'my-cards'
? 'border-primary text-primary'
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
}`}
>
</button>
<button
onClick={() => setActiveTab('all-cards')}
className={`py-4 px-1 border-b-2 font-medium text-sm ${
activeTab === 'all-cards'
? 'border-primary text-primary'
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
}`}
>
</button>
<button
onClick={() => setActiveTab('favorites')}
className={`py-4 px-1 border-b-2 font-medium text-sm ${
activeTab === 'favorites'
? 'border-primary text-primary'
: 'border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300'
}`}
>
</button>
</nav>
</div>
<div className="p-6">
{activeTab === 'my-cards' && (
<div>
<div className="flex justify-between items-center mb-4">
<h3 className="text-lg font-semibold"> {filteredSets.length} </h3>
<div className="flex gap-2">
<button className="p-2 text-gray-600 hover:text-gray-900">
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 10h16M4 14h16M4 18h16" />
</svg>
</button>
<button className="p-2 text-gray-600 hover:text-gray-900">
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2V6zM14 6a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V6zM4 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2H6a2 2 0 01-2-2v-2zM14 16a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2v-2z" />
</svg>
</button>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{filteredSets.map(set => (
<div
key={set.id}
className="border rounded-lg hover:shadow-lg transition-shadow cursor-pointer"
onClick={() => setSelectedSet(set.id)}
>
<div className={`h-2 ${set.color} rounded-t-lg`}></div>
<div className="p-4">
<div className="flex justify-between items-start mb-2">
<h4 className="font-semibold text-lg">{set.name}</h4>
<button className="text-gray-400 hover:text-gray-600">
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 5v.01M12 12v.01M12 19v.01M12 6a1 1 0 110-2 1 1 0 010 2zm0 7a1 1 0 110-2 1 1 0 010 2zm0 7a1 1 0 110-2 1 1 0 010 2z" />
</svg>
</button>
</div>
<p className="text-sm text-gray-600 mb-3">{set.description}</p>
<div className="flex flex-wrap gap-1 mb-3">
{set.tags.map(tag => (
<span key={tag} className="px-2 py-1 bg-gray-100 text-gray-600 text-xs rounded-full">
{tag}
</span>
))}
</div>
<div className="space-y-2">
<div className="flex justify-between text-sm">
<span className="text-gray-600"></span>
<span className="font-medium">{set.progress}%</span>
</div>
<div className="w-full bg-gray-200 rounded-full h-2">
<div
className="bg-primary h-2 rounded-full transition-all"
style={{ width: `${set.progress}%` }}
></div>
</div>
<div className="flex justify-between text-sm text-gray-600">
<span>{set.cardCount} </span>
<span>{set.lastStudied}</span>
</div>
</div>
<div className="mt-4 flex gap-2">
<Link
href={`/learn?set=${set.id}`}
className="flex-1 bg-primary text-white text-center py-2 rounded-lg hover:bg-primary-hover transition-colors text-sm font-medium"
>
</Link>
<button className="flex-1 border border-gray-300 py-2 rounded-lg hover:bg-gray-50 transition-colors text-sm font-medium">
</button>
</div>
</div>
</div>
))}
{/* Add New Set Card */}
<div className="border-2 border-dashed border-gray-300 rounded-lg hover:border-gray-400 transition-colors cursor-pointer flex items-center justify-center min-h-[280px]">
<div className="text-center">
<div className="w-12 h-12 bg-gray-100 rounded-full flex items-center justify-center mx-auto mb-3">
<svg className="w-6 h-6 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v16m8-8H4" />
</svg>
</div>
<p className="text-gray-600 font-medium"></p>
<p className="text-sm text-gray-500 mt-1"></p>
</div>
</div>
</div>
</div>
)}
{activeTab === 'all-cards' && (
<div>
<div className="flex justify-between items-center mb-4">
<h3 className="text-lg font-semibold"> {filteredCards.length} </h3>
<button className="text-primary hover:text-primary-hover font-medium text-sm">
</button>
</div>
<div className="space-y-3">
{filteredCards.map(card => (
<div key={card.id} className="bg-white border rounded-lg p-4 hover:shadow-md transition-shadow">
<div className="flex items-center justify-between">
<div className="flex items-center space-x-4">
<input type="checkbox" className="h-4 w-4 text-primary focus:ring-primary border-gray-300 rounded" />
<div>
<div className="font-semibold">{card.word}</div>
<div className="text-sm text-gray-600">{card.translation}</div>
</div>
</div>
<div className="flex items-center space-x-4">
<div className="text-right">
<div className="text-sm text-gray-600"></div>
<div className="font-medium">{card.mastery}%</div>
</div>
<div className="text-right">
<div className="text-sm text-gray-600"></div>
<div className="text-sm font-medium text-primary">{card.nextReview}</div>
</div>
<button className="text-gray-400 hover:text-gray-600">
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z" />
</svg>
</button>
</div>
</div>
</div>
))}
</div>
</div>
)}
{activeTab === 'favorites' && (
<div className="text-center py-12">
<div className="w-16 h-16 bg-gray-100 rounded-full flex items-center justify-center mx-auto mb-4">
<svg className="w-8 h-8 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z" />
</svg>
</div>
<h3 className="text-lg font-semibold text-gray-900 mb-2"></h3>
<p className="text-gray-600 mb-4"></p>
<Link
href="/learn"
className="inline-block bg-primary text-white px-6 py-2 rounded-lg hover:bg-primary-hover transition-colors"
>
</Link>
</div>
)}
</div>
</div>
{/* Stats Summary */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<div className="bg-white rounded-lg shadow-sm p-4">
<div className="flex items-center justify-between">
<div>
<div className="text-2xl font-bold">234</div>
<div className="text-sm text-gray-600"></div>
</div>
<div className="w-12 h-12 bg-blue-100 rounded-full flex items-center justify-center">
<svg className="w-6 h-6 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10" />
</svg>
</div>
</div>
</div>
<div className="bg-white rounded-lg shadow-sm p-4">
<div className="flex items-center justify-between">
<div>
<div className="text-2xl font-bold">156</div>
<div className="text-sm text-gray-600"></div>
</div>
<div className="w-12 h-12 bg-green-100 rounded-full flex items-center justify-center">
<svg className="w-6 h-6 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
</div>
</div>
<div className="bg-white rounded-lg shadow-sm p-4">
<div className="flex items-center justify-between">
<div>
<div className="text-2xl font-bold">23</div>
<div className="text-sm text-gray-600"></div>
</div>
<div className="w-12 h-12 bg-yellow-100 rounded-full flex items-center justify-center">
<svg className="w-6 h-6 text-yellow-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
</div>
</div>
<div className="bg-white rounded-lg shadow-sm p-4">
<div className="flex items-center justify-between">
<div>
<div className="text-2xl font-bold">67%</div>
<div className="text-sm text-gray-600"></div>
</div>
<div className="w-12 h-12 bg-purple-100 rounded-full flex items-center justify-center">
<svg className="w-6 h-6 text-purple-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 7h8m0 0v8m0-8l-8 8-4-4-6 6" />
</svg>
</div>
</div>
</div>
</div>
</div>
</div>
)
}