'use client' import { useState, useEffect } from 'react' import Link from 'next/link' import { useRouter } from 'next/navigation' import { ProtectedRoute } from '@/components/ProtectedRoute' import { Navigation } from '@/components/Navigation' import { FlashcardForm } from '@/components/FlashcardForm' import { useToast } from '@/components/Toast' import { flashcardsService, type Flashcard } from '@/lib/services/flashcards' import { useFlashcardSearch, type SearchActions } from '@/hooks/useFlashcardSearch' // 重構後的FlashcardsContent組件 function FlashcardsContent() { const router = useRouter() const toast = useToast() const [activeTab, setActiveTab] = useState<'all-cards' | 'favorites'>('all-cards') const [showForm, setShowForm] = useState(false) const [editingCard, setEditingCard] = useState(null) const [showAdvancedSearch, setShowAdvancedSearch] = useState(false) const [totalCounts, setTotalCounts] = useState({ all: 0, favorites: 0 }) // 使用新的搜尋Hook const [searchState, searchActions] = useFlashcardSearch(activeTab) // 例句圖片邏輯 const getExampleImage = (word: string): string | null => { // 只列出真正有例句圖片的詞彙 const imageMap: {[key: string]: string} = { 'evidence': '/images/examples/bring_up.png', // warrants 和 recovering 暫時移除,將顯示新增按鈕 } // 只返回已確認存在的圖片,沒有則返回 null return imageMap[word?.toLowerCase()] || null } // 檢查詞彙是否有例句圖片 const hasExampleImage = (word: string): boolean => { return getExampleImage(word) !== null } // 處理AI生成例句圖片 (預留接口) const handleGenerateExampleImage = (card: Flashcard) => { console.log('準備為詞彙生成例句圖片:', card.word) // TODO: 下階段實現AI生成流程 // router.push(`/generate-image?word=${encodeURIComponent(card.word)}`) } // 初始化數據載入 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 handleFormSuccess = async () => { setShowForm(false) setEditingCard(null) await searchActions.refresh() await loadTotalCounts() } const handleEdit = (card: Flashcard) => { setEditingCard(card) setShowForm(true) } const handleDelete = async (card: Flashcard) => { if (!confirm(`確定要刪除詞卡「${card.word}」嗎?`)) { return } try { const result = await flashcardsService.deleteFlashcard(card.id) if (result.success) { await searchActions.refresh() 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.refresh() 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 */}
{/* Form Modal */} {showForm && ( { setShowForm(false) setEditingCard(null) }} /> )} {/* 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: (word: string) => string | null hasExampleImage: (word: string) => boolean onGenerateExampleImage: (card: Flashcard) => void router: any } function SearchResults({ searchState, activeTab, onEdit, onDelete, onToggleFavorite, getCEFRColor, highlightSearchTerm, getExampleImage, hasExampleImage, onGenerateExampleImage, 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)} 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: (word: string) => string | null hasExampleImage: (word: string) => boolean onGenerateExampleImage: () => void router: any } function FlashcardItem({ card, searchTerm, onEdit, onDelete, onToggleFavorite, getCEFRColor, highlightSearchTerm, getExampleImage, hasExampleImage, onGenerateExampleImage, router }: FlashcardItemProps) { return (
{/* CEFR標註 */}
{(card as any).difficultyLevel || 'A1'}
{/* 例句圖片區域 */}
{hasExampleImage(card.word) ? ( // 有例句圖片時顯示圖片 {`${card.word} { const target = e.target as HTMLImageElement target.style.display = 'none' target.parentElement!.innerHTML = `
圖片載入失敗
` }} /> ) : ( // 沒有例句圖片時顯示新增按鈕 )}
{/* 詞卡信息 */}

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

{card.partOfSpeech || 'unknown'}
{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() { return ( ) }