'use client' import { useState, useEffect } from 'react' import Link from 'next/link' import { useRouter } from 'next/navigation' import { ProtectedRoute } from '@/components/shared/ProtectedRoute' import { Navigation } from '@/components/shared/Navigation' import { FlashcardForm } from '@/components/flashcards/FlashcardForm' import { useToast } from '@/components/shared/Toast' import { flashcardsService, type Flashcard } from '@/lib/services/flashcards' import { imageGenerationService } from '@/lib/services/imageGeneration' import { useFlashcardSearch } from '@/hooks/flashcards/useFlashcardSearch' import { SearchControls } from '@/components/flashcards/SearchControls' import { SearchResults as FlashcardSearchResults } from '@/components/flashcards/SearchResults' import { PaginationControls as SharedPaginationControls } from '@/components/shared/PaginationControls' import { getCEFRColor } from '@/lib/utils/flashcardUtils' // 重構後的FlashcardsContent組件 function FlashcardsContent({ showForm, setShowForm }: { showForm: boolean; setShowForm: (show: boolean) => void }) { const router = useRouter() const toast = useToast() const [activeTab, setActiveTab] = useState<'all-cards' | 'favorites'>('all-cards') const [showAdvancedSearch, setShowAdvancedSearch] = useState(false) const [totalCounts, setTotalCounts] = useState({ all: 0, favorites: 0 }) // 使用新的搜尋Hook const [searchState, searchActions] = useFlashcardSearch(activeTab) // 圖片生成狀態管理 const [generatingCards, setGeneratingCards] = useState>(new Set()) const [generationProgress, setGenerationProgress] = useState<{[cardId: string]: string}>({}) // 例句圖片邏輯 - 使用 API 資料 const getExampleImage = (card: Flashcard): string | null => { return card.primaryImageUrl || null } // 檢查詞彙是否有例句圖片 - 使用 API 資料 const hasExampleImage = (card: Flashcard): boolean => { return card.hasExampleImage } // 處理AI生成例句圖片 - 完整實現 const handleGenerateExampleImage = async (card: Flashcard) => { try { // 檢查是否已在生成中 if (generatingCards.has(card.id)) { toast.error('該詞卡正在生成圖片中,請稍候...') return } // 標記為生成中 setGeneratingCards(prev => new Set([...prev, card.id])) setGenerationProgress(prev => ({ ...prev, [card.id]: '啟動生成中...' })) toast.info(`開始為「${card.word}」生成例句圖片...`) // 1. 啟動圖片生成 const generateResult = await imageGenerationService.generateImage(card.id) if (!generateResult.success || !generateResult.data) { throw new Error(generateResult.error || '啟動生成失敗') } const requestId = generateResult.data.requestId setGenerationProgress(prev => ({ ...prev, [card.id]: 'Gemini 生成描述中...' })) // 2. 輪詢生成進度 const finalStatus = await imageGenerationService.pollUntilComplete( requestId, (status) => { // 更新進度顯示 const stage = status.stages.gemini.status === 'completed' ? 'Replicate 生成圖片中...' : 'Gemini 生成描述中...' setGenerationProgress(prev => ({ ...prev, [card.id]: stage })) }, 5 // 5分鐘超時 ) // 3. 生成成功,刷新資料 if (finalStatus.overallStatus === 'completed') { setGenerationProgress(prev => ({ ...prev, [card.id]: '生成完成,載入中...' })) // 清除快取並重新載入詞卡列表以顯示新圖片 await searchActions.refetch() toast.success(`「${card.word}」的例句圖片生成完成!`) } else { throw new Error('圖片生成未完成') } } catch (error: any) { console.error('圖片生成失敗:', error) toast.error(`圖片生成失敗: ${error.message || '未知錯誤'}`) } finally { // 清理狀態 setGeneratingCards(prev => { const newSet = new Set(prev) newSet.delete(card.id) return newSet }) setGenerationProgress(prev => { const newProgress = { ...prev } delete newProgress[card.id] return newProgress }) } } // 初始化數據載入 useEffect(() => { loadTotalCounts() }, []) // 載入總數統計 const loadTotalCounts = async () => { try { // 載入所有詞卡數量 const allResult = await flashcardsService.getFlashcards() const allCount = allResult.success && allResult.data ? allResult.data.count : 0 // 載入收藏詞卡數量 const favoritesResult = await flashcardsService.getFlashcards(undefined, true) const favoritesCount = favoritesResult.success && favoritesResult.data ? favoritesResult.data.count : 0 setTotalCounts({ all: allCount, favorites: favoritesCount }) } catch (err) { console.error('載入統計失敗:', err) } } const handleEdit = (card: Flashcard) => { router.push(`/flashcards/${card.id}?edit=true`) } const handleDelete = async (card: Flashcard) => { if (!confirm(`確定要刪除詞卡「${card.word}」嗎?`)) { return } try { const result = await flashcardsService.deleteFlashcard(card.id) if (result.success) { await searchActions.refetch() await loadTotalCounts() toast.success(`詞卡「${card.word}」已刪除`) } else { toast.error(result.error || '刪除失敗') } } catch (err) { toast.error('刪除失敗,請重試') } } const handleToggleFavorite = async (card: Flashcard) => { try { const result = await flashcardsService.toggleFavorite(card.id) if (result.success) { await searchActions.refetch() await loadTotalCounts() toast.success(`${card.isFavorite ? '已取消收藏' : '已加入收藏'}「${card.word}」`) } else { toast.error(result.error || '操作失敗') } } catch (err) { toast.error('操作失敗,請重試') } } // 搜尋結果高亮函數 const highlightSearchTerm = (text: string, searchTerm: string) => { if (!searchTerm || !text) return text const regex = new RegExp(`(${searchTerm.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi') const parts = text.split(regex) return parts.map((part, index) => regex.test(part) ? ( {part} ) : ( part ) ) } // 載入狀態處理 if (searchState.loading && searchState.isInitialLoad) { return (
載入中...
) } if (searchState.error) { return (
{searchState.error}
) } return (
{/* Navigation */} {/* Main Content */}
{/* Page Header */}

我的詞卡

AI 生成詞卡
{/* Tabs */}
{/* Search Controls */} {/* 詞卡數目統計 */}

共 {searchState.pagination.totalCount} 個詞卡 {searchState.pagination.totalPages > 1 && ( (第 {searchState.pagination.currentPage} 頁,顯示 {searchState.flashcards.length} 個) )}

{/* Search Results */} {/* Pagination Controls */}
{/* Toast */}
) } export default function FlashcardsPage() { const [showForm, setShowForm] = useState(false) return ( {/* 全域模態框 - 在最外層 */} {showForm && (
setShowForm(false)} >
e.stopPropagation()} >

新增詞卡

{ console.log('詞卡創建成功'); setShowForm(false); // TODO: 刷新詞卡列表 }} onCancel={() => setShowForm(false)} />
)}
) }