403 lines
19 KiB
TypeScript
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>
|
|
)
|
|
} |