193 lines
5.9 KiB
TypeScript
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>
|
|
)
|
|
} |