dramaling-vocab-learning/frontend/app/flashcards/[id]/page.tsx

193 lines
5.9 KiB
TypeScript

'use client'
import { use } from 'react'
import { useRouter } from 'next/navigation'
import { Navigation } from '@/components/shared/Navigation'
import { ProtectedRoute } from '@/components/shared/ProtectedRoute'
import { useToast } from '@/components/shared/Toast'
import { getCEFRColor } from '@/lib/utils/flashcardUtils'
import { useTTSPlayer } from '@/hooks/shared/useTTSPlayer'
import { useFlashcardDetailData } from '@/hooks/flashcards/useFlashcardDetailData'
import { useFlashcardActions } from '@/hooks/flashcards/useFlashcardActions'
import { useImageGeneration } from '@/hooks/flashcards/useImageGeneration'
import { FlashcardDetailHeader } from '@/components/flashcards/FlashcardDetailHeader'
import { FlashcardContentBlocks } from '@/components/flashcards/FlashcardContentBlocks'
import { FlashcardInfoBlock } from '@/components/flashcards/FlashcardInfoBlock'
import { FlashcardActions } from '@/components/flashcards/FlashcardActions'
import { EditingControls } from '@/components/flashcards/EditingControls'
import { LoadingState } from '@/components/shared/LoadingState'
import { ErrorState } from '@/components/shared/ErrorState'
interface FlashcardDetailPageProps {
params: Promise<{
id: string
}>
}
export default function FlashcardDetailPage({ params }: FlashcardDetailPageProps) {
const { id } = use(params)
return (
<ProtectedRoute>
<FlashcardDetailContent cardId={id} />
</ProtectedRoute>
)
}
function FlashcardDetailContent({ cardId }: { cardId: string }) {
const router = useRouter()
const toast = useToast()
// 使用數據管理Hook
const {
flashcard,
loading,
error,
isEditing,
editedCard,
setFlashcard,
setIsEditing,
setEditedCard
} = useFlashcardDetailData(cardId)
// 使用TTS Hook
const { isPlayingWord, isPlayingExample, toggleWordTTS, toggleExampleTTS } = useTTSPlayer()
// 使用業務邏輯Hooks
const { toggleFavorite, saveEdit, deleteFlashcard, isLoading: isActionLoading } = useFlashcardActions({
flashcard,
editedCard,
onFlashcardUpdate: setFlashcard,
onEditingChange: setIsEditing
})
const { generateImage, isGenerating: isGeneratingImage, progress: generationProgress } = useImageGeneration({
flashcard,
onFlashcardUpdate: setFlashcard
})
// 編輯變更處理函數
const handleEditChange = (field: string, value: string) => {
setEditedCard((prev: any) => ({ ...prev, [field]: value }))
}
// 編輯操作處理
const handleToggleEdit = () => {
if (isEditing) {
setEditedCard(flashcard)
}
setIsEditing(!isEditing)
}
const handleCancelEdit = () => {
setIsEditing(false)
setEditedCard(flashcard)
}
if (loading) {
return <LoadingState />
}
if (error || !flashcard) {
return (
<ErrorState
error={error || '詞卡不存在'}
onGoBack={() => router.push('/flashcards')}
/>
)
}
return (
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100">
<Navigation />
<div className="max-w-4xl mx-auto px-4 py-8">
{/* 導航欄 */}
<div className="flex items-center justify-between mb-6">
<div className="flex items-center gap-3">
<button
onClick={() => router.push('/flashcards')}
className="text-gray-600 hover:text-gray-900 flex items-center gap-2"
>
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
</svg>
</button>
</div>
</div>
{/* 主要詞卡內容 */}
<div className="bg-white rounded-xl shadow-lg overflow-hidden mb-6 relative">
{/* CEFR標籤 - 右上角 */}
<div className="absolute top-4 right-4 z-10">
<span className={`px-3 py-1 rounded-full text-sm font-medium border ${getCEFRColor(flashcard.cefr || 'A1')}`}>
{flashcard.cefr || 'A1'}
</span>
</div>
{/* 標題區 */}
<FlashcardDetailHeader
flashcard={flashcard}
isPlayingWord={isPlayingWord}
isPlayingExample={isPlayingExample}
onToggleWordTTS={toggleWordTTS}
/>
{/* 內容區塊 */}
<FlashcardContentBlocks
flashcard={flashcard}
isEditing={isEditing}
editedCard={editedCard}
onEditChange={handleEditChange}
isPlayingWord={isPlayingWord}
isPlayingExample={isPlayingExample}
onToggleExampleTTS={toggleExampleTTS}
isGeneratingImage={isGeneratingImage}
generationProgress={generationProgress}
onGenerateImage={generateImage}
/>
{/* 詞卡資訊區塊 */}
<div className="px-6">
<FlashcardInfoBlock
flashcard={flashcard}
isEditing={isEditing}
editedCard={editedCard}
onEditChange={handleEditChange}
/>
</div>
{/* 編輯模式控制 */}
{isEditing && (
<EditingControls
onSave={saveEdit}
onCancel={handleCancelEdit}
isSaving={isActionLoading}
/>
)}
</div>
{/* 底部操作區 */}
<FlashcardActions
flashcard={flashcard}
isEditing={isEditing}
onToggleFavorite={toggleFavorite}
onToggleEdit={handleToggleEdit}
onDelete={deleteFlashcard}
/>
{/* Toast 通知系統 */}
<toast.ToastContainer />
</div>
</div>
)
}