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 || '詞卡不存在'}
+ +
+
+ ) + } + + return ( +
+ + +
+ {/* 導航欄 */} +
+
+ +
+ +
+ + CEFR {flashcard.difficultyLevel || 'A1'} + + {flashcard.isFavorite && ( + + )} +
+
+ + {/* 主要詞卡內容 - 學習功能風格 */} +
+ {/* 標題區 */} +
+
+
+
+ {flashcard.word[0].toUpperCase()} +
+
+

{flashcard.word}

+
+ {flashcard.pronunciation} + + + {flashcard.partOfSpeech} + +
+
+
+ +
+ + + +
+
+ + {/* 學習統計 */} +
+
+
{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 ? ( +