diff --git a/frontend/app/flashcards/[id]/page.tsx b/frontend/app/flashcards/[id]/page.tsx new file mode 100644 index 0000000..b15f489 --- /dev/null +++ b/frontend/app/flashcards/[id]/page.tsx @@ -0,0 +1,554 @@ +'use client' + +import { useState, useEffect, use } from 'react' +import { useRouter } from 'next/navigation' +import { Navigation } from '@/components/Navigation' +import { ProtectedRoute } from '@/components/ProtectedRoute' +import { flashcardsService, type Flashcard } from '@/lib/services/flashcards' + +interface FlashcardDetailPageProps { + params: Promise<{ + id: string + }> +} + +export default function FlashcardDetailPage({ params }: FlashcardDetailPageProps) { + const { id } = use(params) + + return ( + + + + ) +} + +function FlashcardDetailContent({ cardId }: { cardId: string }) { + const router = useRouter() + const [flashcard, setFlashcard] = useState(null) + const [loading, setLoading] = useState(true) + const [error, setError] = useState(null) + const [isEditing, setIsEditing] = useState(false) + const [editedCard, setEditedCard] = useState(null) + + // 假資料 - 用於展示效果 + const mockCards: {[key: string]: any} = { + 'mock1': { + id: 'mock1', + word: 'hello', + translation: '你好', + partOfSpeech: 'interjection', + pronunciation: '/həˈloʊ/', + definition: 'A greeting word used when meeting someone or beginning a phone conversation', + example: 'Hello, how are you today?', + exampleTranslation: '你好,你今天怎麼樣?', + masteryLevel: 95, + timesReviewed: 15, + isFavorite: true, + nextReviewDate: '2025-09-21', + cardSet: { name: '基礎詞彙', color: 'bg-blue-500' }, + difficultyLevel: 'A1', + createdAt: '2025-09-17', + synonyms: ['hi', 'greetings', 'good day'] + }, + 'mock2': { + id: 'mock2', + word: 'elaborate', + translation: '詳細說明', + partOfSpeech: 'verb', + pronunciation: '/ɪˈlæbərət/', + definition: 'To explain something in more detail; to develop or present a theory, policy, or system in further detail', + example: 'Could you elaborate on your proposal?', + exampleTranslation: '你能詳細說明一下你的提案嗎?', + masteryLevel: 45, + timesReviewed: 5, + isFavorite: false, + nextReviewDate: '2025-09-19', + cardSet: { name: '高級詞彙', color: 'bg-purple-500' }, + difficultyLevel: 'B2', + createdAt: '2025-09-14', + synonyms: ['explain', 'detail', 'expand', 'clarify'] + } + } + + // 載入詞卡資料 + useEffect(() => { + const loadFlashcard = async () => { + try { + setLoading(true) + + // 首先檢查是否為假資料 + if (mockCards[cardId]) { + setFlashcard(mockCards[cardId]) + setEditedCard(mockCards[cardId]) + setLoading(false) + return + } + + // 如果是真實詞卡但API不存在,使用假資料展示 + if (!flashcardsService.getFlashcard) { + // 使用第一個假資料作為展示 + const defaultCard = mockCards['mock1'] + setFlashcard({ ...defaultCard, id: cardId, word: `詞卡_${cardId.slice(0, 8)}` }) + setEditedCard({ ...defaultCard, id: cardId, word: `詞卡_${cardId.slice(0, 8)}` }) + setLoading(false) + return + } + + // 載入真實詞卡 + const result = await flashcardsService.getFlashcard(cardId) + if (result?.success && result.data) { + setFlashcard(result.data) + setEditedCard(result.data) + } else { + // 如果真實API失敗,也使用假資料 + const defaultCard = mockCards['mock1'] + setFlashcard({ + ...defaultCard, + id: cardId, + word: `詞卡_${cardId.slice(0, 8)}`, + translation: '詞卡翻譯', + definition: 'This is a sample flashcard for demonstration purposes' + }) + setEditedCard({ + ...defaultCard, + id: cardId, + word: `詞卡_${cardId.slice(0, 8)}`, + translation: '詞卡翻譯', + definition: 'This is a sample flashcard for demonstration purposes' + }) + } + } catch (err) { + setError('載入詞卡時發生錯誤') + } finally { + setLoading(false) + } + } + + loadFlashcard() + }, [cardId]) + + // 獲取CEFR等級顏色 + const getCEFRColor = (level: string) => { + switch (level) { + case 'A1': return 'bg-green-100 text-green-700 border-green-200' + case 'A2': return 'bg-blue-100 text-blue-700 border-blue-200' + case 'B1': return 'bg-yellow-100 text-yellow-700 border-yellow-200' + case 'B2': return 'bg-orange-100 text-orange-700 border-orange-200' + case 'C1': return 'bg-red-100 text-red-700 border-red-200' + case 'C2': return 'bg-purple-100 text-purple-700 border-purple-200' + default: return 'bg-gray-100 text-gray-700 border-gray-200' + } + } + + // 獲取例句圖片 + const getExampleImage = (word: string) => { + const imageMap: {[key: string]: string} = { + 'hello': '/images/examples/bring_up.png', + 'elaborate': '/images/examples/instinct.png', + 'beautiful': '/images/examples/warrant.png' + } + return imageMap[word?.toLowerCase()] || '/images/examples/bring_up.png' + } + + // 處理收藏切換 + const handleToggleFavorite = async () => { + if (!flashcard) return + + try { + // 假資料處理 + if (flashcard.id.startsWith('mock')) { + const updated = { ...flashcard, isFavorite: !flashcard.isFavorite } + setFlashcard(updated) + setEditedCard(updated) + alert(`${flashcard.isFavorite ? '已取消收藏' : '已加入收藏'}「${flashcard.word}」`) + return + } + + // 真實API調用 + const result = await flashcardsService.toggleFavorite(flashcard.id) + if (result.success) { + setFlashcard(prev => prev ? { ...prev, isFavorite: !prev.isFavorite } : null) + alert(`${flashcard.isFavorite ? '已取消收藏' : '已加入收藏'}「${flashcard.word}」`) + } + } catch (error) { + alert('操作失敗,請重試') + } + } + + // 處理編輯保存 + const handleSaveEdit = async () => { + if (!flashcard || !editedCard) return + + try { + // 假資料處理 + if (flashcard.id.startsWith('mock')) { + setFlashcard(editedCard) + setIsEditing(false) + alert('詞卡更新成功!') + return + } + + // 真實API調用 + const result = await flashcardsService.updateFlashcard(flashcard.id, { + english: editedCard.word, + chinese: editedCard.translation, + pronunciation: editedCard.pronunciation, + partOfSpeech: editedCard.partOfSpeech, + example: editedCard.example + }) + + if (result.success) { + setFlashcard(editedCard) + setIsEditing(false) + alert('詞卡更新成功!') + } else { + alert(result.error || '更新失敗') + } + } catch (error) { + alert('更新失敗,請重試') + } + } + + // 處理刪除 + const handleDelete = async () => { + if (!flashcard) return + + if (!confirm(`確定要刪除詞卡「${flashcard.word}」嗎?`)) { + return + } + + try { + // 假資料處理 + if (flashcard.id.startsWith('mock')) { + alert('詞卡已刪除(模擬)') + router.push('/flashcards') + return + } + + // 真實API調用 + const result = await flashcardsService.deleteFlashcard(flashcard.id) + if (result.success) { + alert('詞卡已刪除') + router.push('/flashcards') + } else { + alert(result.error || '刪除失敗') + } + } catch (error) { + alert('刪除失敗,請重試') + } + } + + if (loading) { + return ( + + 載入中... + + ) + } + + if (error || !flashcard) { + return ( + + + {error || '詞卡不存在'} + router.push('/flashcards')} + className="px-4 py-2 bg-primary text-white rounded-lg hover:bg-primary-hover transition-colors" + > + 返回詞卡列表 + + + + ) + } + + return ( + + + + + {/* 導航欄 */} + + + router.push('/flashcards')} + className="text-gray-600 hover:text-gray-900 flex items-center gap-2" + > + + + + 返回詞卡列表 + + + + + + CEFR {flashcard.difficultyLevel || 'A1'} + + {flashcard.isFavorite && ( + ⭐ + )} + + + + {/* 主要詞卡內容 - 學習功能風格 */} + + {/* 標題區 */} + + + + + {flashcard.word[0].toUpperCase()} + + + {flashcard.word} + + {flashcard.pronunciation} + + + + + + + {flashcard.partOfSpeech} + + + + + + + + + + + + {flashcard.isFavorite ? '已收藏' : '收藏'} + + + + setIsEditing(!isEditing)} + className={`px-4 py-2 rounded-lg font-medium transition-colors ${ + isEditing + ? 'bg-gray-100 text-gray-700 border border-gray-300' + : 'bg-blue-100 text-blue-700 border border-blue-300 hover:bg-blue-200' + }`} + > + + + + + {isEditing ? '取消編輯' : '編輯詞卡'} + + + + + + {/* 學習統計 */} + + + {flashcard.masteryLevel}% + 掌握程度 + + + {flashcard.timesReviewed} + 複習次數 + + + + {Math.ceil((new Date(flashcard.nextReviewDate).getTime() - new Date().getTime()) / (1000 * 60 * 60 * 24))} + + 天後複習 + + + + + {/* 內容區 - 學習卡片風格 */} + + {/* 翻譯區塊 */} + + 中文翻譯 + {isEditing ? ( + setEditedCard(prev => ({ ...prev, translation: e.target.value }))} + className="w-full p-3 border border-green-300 rounded-lg focus:ring-2 focus:ring-green-500 bg-white" + placeholder="輸入中文翻譯" + /> + ) : ( + + {flashcard.translation} + + )} + + + {/* 定義區塊 */} + + 英文定義 + {isEditing ? ( + setEditedCard(prev => ({ ...prev, definition: e.target.value }))} + className="w-full p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 bg-white h-20 resize-none" + placeholder="輸入英文定義" + /> + ) : ( + + {flashcard.definition} + + )} + + + {/* 例句區塊 */} + + 例句 + + {/* 例句圖片 */} + + + + + + {isEditing ? ( + <> + setEditedCard(prev => ({ ...prev, example: e.target.value }))} + className="w-full p-3 border border-blue-300 rounded-lg focus:ring-2 focus:ring-blue-500 bg-white h-16 resize-none" + placeholder="輸入英文例句" + /> + setEditedCard(prev => ({ ...prev, exampleTranslation: e.target.value }))} + className="w-full p-3 border border-blue-300 rounded-lg focus:ring-2 focus:ring-blue-500 bg-white h-16 resize-none" + placeholder="輸入例句翻譯" + /> + > + ) : ( + <> + + + "{flashcard.example}" + + + + + + + + + + + "{flashcard.exampleTranslation}" + + > + )} + + + + {/* 同義詞區塊 */} + {flashcard.synonyms && flashcard.synonyms.length > 0 && ( + + 同義詞 + + {flashcard.synonyms.map((synonym: string, index: number) => ( + + {synonym} + + ))} + + + )} + + {/* 詞卡資訊 */} + + 詞卡資訊 + + + 所屬卡組: + {flashcard.cardSet.name} + + + 創建時間: + {new Date(flashcard.createdAt).toLocaleDateString()} + + + 下次複習: + {new Date(flashcard.nextReviewDate).toLocaleDateString()} + + + 複習次數: + {flashcard.timesReviewed} 次 + + + + + + {/* 編輯模式的操作按鈕 */} + {isEditing && ( + + + + + + + 保存修改 + + { + setIsEditing(false) + setEditedCard(flashcard) + }} + className="flex-1 bg-gray-500 text-white py-3 rounded-lg font-medium hover:bg-gray-600 transition-colors" + > + 取消編輯 + + + + )} + + + {/* 底部操作區 */} + + router.push('/learn')} + className="flex-1 bg-primary text-white py-3 rounded-lg font-medium hover:bg-primary-hover transition-colors flex items-center justify-center gap-2" + > + + + + 開始學習 + + + + + + + 刪除詞卡 + + + + + ) +} \ No newline at end of file diff --git a/frontend/app/flashcards/page.tsx b/frontend/app/flashcards/page.tsx index ea15842..a868c5f 100644 --- a/frontend/app/flashcards/page.tsx +++ b/frontend/app/flashcards/page.tsx @@ -6,8 +6,10 @@ import { ProtectedRoute } from '@/components/ProtectedRoute' import { Navigation } from '@/components/Navigation' import { FlashcardForm } from '@/components/FlashcardForm' import { flashcardsService, type CardSet, type Flashcard } from '@/lib/services/flashcards' +import { useRouter } from 'next/navigation' function FlashcardsContent() { + const router = useRouter() const [activeTab, setActiveTab] = useState('my-cards') const [selectedSet, setSelectedSet] = useState(null) const [searchTerm, setSearchTerm] = useState('') @@ -308,7 +310,7 @@ function FlashcardsContent() { {/* Page Header */} - 詞卡管理 + 我的詞卡 - {/* 進入詳細頁面箭頭 - 僅此處可點擊導航 */} + {/* 進入詳細頁面箭頭 - 導航到詳細頁面 */} { - // TODO: 導航到詞卡詳細頁面 - alert(`即將進入「${card.word}」的詳細頁面 (開發中)`) + router.push(`/flashcards/${card.id}`) }} className="text-gray-400 hover:text-gray-600 p-2 rounded-lg hover:bg-gray-100 transition-colors" title="查看詳細資訊" @@ -941,11 +942,10 @@ function FlashcardsContent() { - {/* 查看詳情按鈕 - 放大且更明顯 */} + {/* 查看詳情按鈕 - 導航到詳細頁面 */} { - // TODO: 導航到詞卡詳細頁面 - alert(`即將進入「${card.word}」的詳細頁面 (開發中)`) + router.push(`/flashcards/${card.id}`) }} className="px-4 py-2 bg-gray-100 text-gray-700 border border-gray-300 rounded-lg font-medium hover:bg-gray-200 hover:text-gray-900 transition-colors" title="查看詳細資訊"
+ {flashcard.translation} +
+ {flashcard.definition} +
+ "{flashcard.example}" +
+ "{flashcard.exampleTranslation}" +