'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, type SearchActions } from '@/hooks/flashcards/useFlashcardSearch' import { getPartOfSpeechDisplay } from '@/lib/utils/flashcardUtils' import { FlashcardCard } from '@/components/flashcards/FlashcardCard' import { PaginationControls as SharedPaginationControls } from '@/components/shared/PaginationControls' // 重構後的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('操作失敗,請重試') } } // 獲取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 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 */}
) } // 搜尋控制組件 interface SearchControlsProps { searchState: any searchActions: SearchActions showAdvancedSearch: boolean setShowAdvancedSearch: (show: boolean) => void } function SearchControls({ searchState, searchActions, showAdvancedSearch, setShowAdvancedSearch }: SearchControlsProps) { return (

搜尋詞卡

{/* 排序控件 */}
排序:
{/* 主要搜尋框 */}
searchActions.updateFilters({ search: e.target.value })} placeholder="搜尋詞彙、翻譯或定義..." className="w-full pl-12 pr-20 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent text-base" onKeyDown={(e) => { if (e.key === 'Escape') { searchActions.clearFilters() } }} />
{(searchState.filters.search || (searchState as any).hasActiveFilters) && (
)}
{/* 進階篩選面板 */} {showAdvancedSearch && (
{/* CEFR等級篩選 */}
{/* 詞性篩選 */}
{/* 掌握度篩選 */}
{/* 收藏篩選 */}
)}
) } // 搜尋結果組件 interface SearchResultsProps { searchState: any activeTab: string onEdit: (card: Flashcard) => void onDelete: (card: Flashcard) => void onToggleFavorite: (card: Flashcard) => void getCEFRColor: (level: string) => string highlightSearchTerm: (text: string, term: string) => React.ReactNode getExampleImage: (card: Flashcard) => string | null hasExampleImage: (card: Flashcard) => boolean onGenerateExampleImage: (card: Flashcard) => void generatingCards: Set generationProgress: {[cardId: string]: string} router: any } function SearchResults({ searchState, activeTab, onEdit, onDelete, onToggleFavorite, getCEFRColor, highlightSearchTerm, getExampleImage, hasExampleImage, onGenerateExampleImage, generatingCards, generationProgress, router }: SearchResultsProps) { if (searchState.flashcards.length === 0) { return (
{activeTab === 'favorites' ? ( <>

還沒有收藏的詞卡

在詞卡列表中點擊星星按鈕來收藏重要的詞彙

) : ( <>

沒有找到詞卡

創建新詞卡 )}
) } return (
{searchState.flashcards.map((card: Flashcard) => ( onEdit(card)} onDelete={() => onDelete(card)} onToggleFavorite={() => onToggleFavorite(card)} getCEFRColor={getCEFRColor} highlightSearchTerm={highlightSearchTerm} getExampleImage={getExampleImage} hasExampleImage={hasExampleImage} onGenerateExampleImage={() => onGenerateExampleImage(card)} generatingCards={generatingCards} generationProgress={generationProgress} router={router} /> ))}
) } // 詞卡項目組件 interface FlashcardItemProps { card: Flashcard searchTerm: string onEdit: () => void onDelete: () => void onToggleFavorite: () => void getCEFRColor: (level: string) => string highlightSearchTerm: (text: string, term: string) => React.ReactNode getExampleImage: (card: Flashcard) => string | null hasExampleImage: (card: Flashcard) => boolean onGenerateExampleImage: () => void generatingCards: Set generationProgress: {[cardId: string]: string} router: any } function FlashcardItem({ card, searchTerm, onEdit, onDelete, onToggleFavorite, getCEFRColor, highlightSearchTerm, getExampleImage, hasExampleImage, onGenerateExampleImage, generatingCards, generationProgress, router }: FlashcardItemProps) { return (
{/* CEFR標註 */}
{card.cefr || 'A1'}
{/* 例句圖片區域 - 響應式設計 */}
{hasExampleImage(card) ? ( // 有例句圖片時顯示圖片 {`${card.word} { const target = e.target as HTMLImageElement target.style.display = 'none' target.parentElement!.innerHTML = `
圖片載入失敗
` }} /> ) : ( // 沒有例句圖片時顯示新增按鈕
新增例句圖
)}
{/* 詞卡信息 */}

{searchTerm ? highlightSearchTerm(card.word || '未設定', searchTerm) : (card.word || '未設定')}

{getPartOfSpeechDisplay(card.partOfSpeech)}
{searchTerm ? highlightSearchTerm(card.translation || '未設定', searchTerm) : (card.translation || '未設定')} {card.pronunciation && (
{card.pronunciation}
)}
創建: {new Date(card.createdAt).toLocaleDateString()} 掌握度: {card.masteryLevel}%
{/* 操作按鈕 - 響應式設計 */}
{/* 收藏按鈕 */} {/* 編輯按鈕 */} {/* 刪除按鈕 */} {/* 詳細按鈕 */}
) } // 分頁控制組件 interface PaginationControlsProps { searchState: any searchActions: SearchActions } function PaginationControls({ searchState, searchActions }: PaginationControlsProps) { if (searchState.pagination.totalPages <= 1) { return null } return (
第 {searchState.pagination.currentPage} 頁,共 {searchState.pagination.totalPages} 頁
每頁顯示:
{/* 上一頁 */} {/* 頁碼 */}
{[...Array(Math.min(5, searchState.pagination.totalPages))].map((_, index) => { let pageNum if (searchState.pagination.totalPages <= 5) { pageNum = index + 1 } else if (searchState.pagination.currentPage <= 3) { pageNum = index + 1 } else if (searchState.pagination.currentPage >= searchState.pagination.totalPages - 2) { pageNum = searchState.pagination.totalPages - 4 + index } else { pageNum = searchState.pagination.currentPage - 2 + index } return ( ) })}
{/* 下一頁 */}
) } export default function FlashcardsPage() { const [showForm, setShowForm] = useState(false) return ( {/* 全域模態框 - 在最外層 */} {showForm && (
setShowForm(false)} >
e.stopPropagation()} >

新增詞卡

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