Module Update
This commit is contained in:
133
src/modules/textbooks/components/textbook-content-layout.tsx
Normal file
133
src/modules/textbooks/components/textbook-content-layout.tsx
Normal file
@@ -0,0 +1,133 @@
|
||||
"use client"
|
||||
|
||||
import { useState } from "react"
|
||||
import { Chapter, KnowledgePoint } from "../types"
|
||||
import { ChapterSidebarList } from "./chapter-sidebar-list"
|
||||
import { KnowledgePointPanel } from "./knowledge-point-panel"
|
||||
import { ScrollArea } from "@/shared/components/ui/scroll-area"
|
||||
import { Button } from "@/shared/components/ui/button"
|
||||
import { Edit2, Save, Plus } from "lucide-react"
|
||||
import { CreateChapterDialog } from "./create-chapter-dialog"
|
||||
import { updateChapterContentAction } from "../actions"
|
||||
import { toast } from "sonner"
|
||||
import { Textarea } from "@/shared/components/ui/textarea"
|
||||
|
||||
interface TextbookContentLayoutProps {
|
||||
chapters: Chapter[]
|
||||
knowledgePoints: KnowledgePoint[]
|
||||
textbookId: string
|
||||
}
|
||||
|
||||
export function TextbookContentLayout({ chapters, knowledgePoints, textbookId }: TextbookContentLayoutProps) {
|
||||
const [selectedChapter, setSelectedChapter] = useState<Chapter | null>(null)
|
||||
const [isEditing, setIsEditing] = useState(false)
|
||||
const [editContent, setEditContent] = useState("")
|
||||
const [isSaving, setIsSaving] = useState(false)
|
||||
|
||||
// Sync edit content when selection changes
|
||||
const handleSelectChapter = (chapter: Chapter) => {
|
||||
setSelectedChapter(chapter)
|
||||
setEditContent(chapter.content || "")
|
||||
setIsEditing(false)
|
||||
}
|
||||
|
||||
const handleSaveContent = async () => {
|
||||
if (!selectedChapter) return
|
||||
setIsSaving(true)
|
||||
const result = await updateChapterContentAction(selectedChapter.id, editContent, textbookId)
|
||||
setIsSaving(false)
|
||||
|
||||
if (result.success) {
|
||||
toast.success(result.message)
|
||||
setIsEditing(false)
|
||||
// Update local state to reflect change immediately (optimistic-like)
|
||||
selectedChapter.content = editContent
|
||||
} else {
|
||||
toast.error(result.message)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-12 gap-6 h-[calc(100vh-140px)]">
|
||||
{/* Left Sidebar: TOC (3 cols) */}
|
||||
<div className="col-span-3 border-r pr-6 flex flex-col h-full">
|
||||
<div className="flex items-center justify-between mb-4 px-2">
|
||||
<h3 className="font-semibold">Chapters</h3>
|
||||
<CreateChapterDialog textbookId={textbookId} />
|
||||
</div>
|
||||
<ScrollArea className="flex-1 -mx-2 px-2">
|
||||
<ChapterSidebarList
|
||||
chapters={chapters}
|
||||
selectedChapterId={selectedChapter?.id}
|
||||
onSelectChapter={handleSelectChapter}
|
||||
/>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
|
||||
{/* Middle: Content Viewer/Editor (6 cols) */}
|
||||
<div className="col-span-6 flex flex-col h-full px-2">
|
||||
{selectedChapter ? (
|
||||
<>
|
||||
<div className="flex items-center justify-between mb-4 pb-2 border-b">
|
||||
<h2 className="text-xl font-bold tracking-tight">{selectedChapter.title}</h2>
|
||||
<div className="flex gap-2">
|
||||
{isEditing ? (
|
||||
<>
|
||||
<Button size="sm" variant="ghost" onClick={() => setIsEditing(false)} disabled={isSaving}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button size="sm" onClick={handleSaveContent} disabled={isSaving}>
|
||||
<Save className="mr-2 h-4 w-4" />
|
||||
{isSaving ? "Saving..." : "Save"}
|
||||
</Button>
|
||||
</>
|
||||
) : (
|
||||
<Button size="sm" variant="outline" onClick={() => setIsEditing(true)}>
|
||||
<Edit2 className="mr-2 h-4 w-4" />
|
||||
Edit Content
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ScrollArea className="flex-1">
|
||||
<div className="p-4 h-full">
|
||||
{isEditing ? (
|
||||
<Textarea
|
||||
className="min-h-[500px] font-mono text-sm"
|
||||
value={editContent}
|
||||
onChange={(e) => setEditContent(e.target.value)}
|
||||
placeholder="# Write markdown content here..."
|
||||
/>
|
||||
) : (
|
||||
<div className="prose prose-sm dark:prose-invert max-w-none">
|
||||
{selectedChapter.content ? (
|
||||
<div className="whitespace-pre-wrap">{selectedChapter.content}</div>
|
||||
) : (
|
||||
<div className="text-muted-foreground italic py-8 text-center">
|
||||
No content available. Click edit to add content.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</>
|
||||
) : (
|
||||
<div className="h-full flex items-center justify-center text-muted-foreground">
|
||||
Select a chapter from the left sidebar to view its content.
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Right Sidebar: Knowledge Points (3 cols) */}
|
||||
<div className="col-span-3 border-l pl-6 flex flex-col h-full">
|
||||
<KnowledgePointPanel
|
||||
knowledgePoints={knowledgePoints}
|
||||
selectedChapterId={selectedChapter?.id || null}
|
||||
textbookId={textbookId}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user