"use client" import { useState, useEffect, Suspense } from "react" import { useSearchParams } from "next/navigation" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { Textarea } from "@/components/ui/textarea" import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, } from "@/components/ui/dialog" import { FileText, Loader2, Plus, ArrowLeft, Wand2, Edit, Trash2 } from "lucide-react" import { toast } from "sonner" import Link from "next/link" interface Chapter { _id: string number: number title: string views: number createdAt: string } const generatePagination = (currentPage: number, totalPages: number) => { if (totalPages <= 7) { return Array.from({ length: totalPages }, (_, i) => i + 1) } if (currentPage <= 3) { return [1, 2, 3, 4, '...', totalPages] } if (currentPage >= totalPages - 2) { return [1, '...', totalPages - 3, totalPages - 2, totalPages - 1, totalPages] } return [1, '...', currentPage - 1, currentPage, currentPage + 1, '...', totalPages] } function ChapterManager() { const searchParams = useSearchParams() const novelId = searchParams.get("novelId") const [chapters, setChapters] = useState([]) const [loading, setLoading] = useState(true) const [openAdd, setOpenAdd] = useState(false) const [submitting, setSubmitting] = useState(false) // Pagination states const [currentPage, setCurrentPage] = useState(1) const [totalPages, setTotalPages] = useState(1) const [totalChapters, setTotalChapters] = useState(0) // Optimization states const [openOptimize, setOpenOptimize] = useState(false) const [previewMode, setPreviewMode] = useState(false) const [optimizing, setOptimizing] = useState(false) const [optRemovePrefix, setOptRemovePrefix] = useState(true) const [optRenumber, setOptRenumber] = useState(true) const [optimizedChapters, setOptimizedChapters] = useState([]) // Edit states const [openEdit, setOpenEdit] = useState(false) const [editingChapterId, setEditingChapterId] = useState(null) const [loadingEditData, setLoadingEditData] = useState(false) // Delete states const [openDelete, setOpenDelete] = useState(false) const [deletingChapterId, setDeletingChapterId] = useState(null) // Form states const [number, setNumber] = useState("") const [title, setTitle] = useState("") const [content, setContent] = useState("") const fetchChapters = async (pageToFetch = 1) => { if (!novelId) return setLoading(true) try { const res = await fetch(`/api/mod/chuong?novelId=${novelId}&page=${pageToFetch}&limit=50`) if (!res.ok) throw new Error("Lỗi fetch") const data = await res.json() setChapters(data.chapters) setTotalPages(data.totalPages) setCurrentPage(data.currentPage) setTotalChapters(data.totalChapters) if (data.chapters.length > 0) { setNumber((data.chapters[data.chapters.length - 1].number + 1).toString()) } else { setNumber("1") } } catch { toast.error("Không tải được danh sách chương") } finally { setLoading(false) } } useEffect(() => { if (novelId) { fetchChapters(currentPage) } }, [novelId, currentPage]) const handleAddSubmit = async (e: React.FormEvent) => { e.preventDefault() if (!number || !title || !content || !novelId) { toast.error("Vui lòng điền đầy đủ") return } setSubmitting(true) try { const res = await fetch("/api/mod/chuong", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ novelId, number: parseInt(number), title, content }), }) const resData = await res.json() if (!res.ok) throw new Error(resData.error || "Thêm mới thất bại") toast.success("Đã đăng chương mới thành công!") setOpenAdd(false) setTitle("") setContent("") setNumber((parseInt(number) + 1).toString()) fetchChapters() } catch (error: any) { toast.error(error.message) } finally { setSubmitting(false) } } const handlePreviewOptimize = () => { let newChapters = [...chapters] if (optRenumber) { newChapters.sort((a, b) => a.number - b.number) newChapters = newChapters.map((ch, idx) => ({ ...ch, number: idx + 1 })) } if (optRemovePrefix) { newChapters = newChapters.map((ch, i) => { let newTitle = ch.title.replace(/^(Chương|Ch\.)\s*\d+\s*[:-]?\s*/i, "") if (!newTitle) newTitle = `Chương ${ch.number}` return { ...ch, title: newTitle } }) } setOptimizedChapters(newChapters) setPreviewMode(true) } const handleApplyOptimize = async () => { if (optimizedChapters.length === 0) return setOptimizing(true) try { const updates = optimizedChapters.map(ch => ({ id: ch._id, title: ch.title, number: ch.number })) const res = await fetch("/api/mod/chuong/optimize", { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ novelId, updates }), }) const data = await res.json() if (!res.ok) throw new Error(data.error || "Lỗi tối ưu hóa") toast.success(`Đã tổi ưu ${data.modifiedCount} chương!`) setOpenOptimize(false) setPreviewMode(false) fetchChapters(currentPage) } catch (error: any) { toast.error(error.message) } finally { setOptimizing(false) } } const handleOpenEdit = async (chapter: Chapter) => { setEditingChapterId(chapter._id) setNumber(chapter.number.toString()) setTitle(chapter.title) setContent("") // Khởi tạo rỗng trong lúc chờ fetch nội dung setOpenEdit(true) setLoadingEditData(true) try { // Lấy chi tiết chương từ list db để có nội dung qua API GET /api/mod/chuong/[id] vừa tạo const res = await fetch(`/api/mod/chuong/${chapter._id}`) if (res.ok) { const data = await res.json() setContent(data.content) } else { toast.error("Không tải được nội dung chương") } } catch { toast.error("Không tải được nội dung chương") } finally { setLoadingEditData(false) } } const handleEditSubmit = async (e: React.FormEvent) => { e.preventDefault() if (!number || !title || !content || !novelId || !editingChapterId) { toast.error("Vui lòng điền đầy đủ") return } setSubmitting(true) try { const res = await fetch("/api/mod/chuong", { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ id: editingChapterId, novelId, number: parseInt(number), title, content }), }) const resData = await res.json() if (!res.ok) throw new Error(resData.error || "Cập nhật thất bại") toast.success("Đã cập nhật chương thành công!") setOpenEdit(false) fetchChapters() } catch (error: any) { toast.error(error.message) } finally { setSubmitting(false) } } const handleDelete = async () => { if (!deletingChapterId || !novelId) return setSubmitting(true) try { const res = await fetch(`/api/mod/chuong?id=${deletingChapterId}&novelId=${novelId}`, { method: "DELETE", }) if (!res.ok) { const data = await res.json() throw new Error(data.error || "Xóa thất bại") } toast.success("Đã xóa chương thành công") setOpenDelete(false) fetchChapters() } catch (error: any) { toast.error(error.message) } finally { setSubmitting(false) } } if (!novelId) { return (
Vui lòng chọn một truyện từ mục Quản lý truyện để xem danh sách chương.
) } return (

Quản lý chương

Đăng Chương Mới Thêm nội dung một chương truyện để gửi đến độc giả.
setNumber(e.target.value)} required />
setTitle(e.target.value)} placeholder="Ví dụ: Thiếu niên kỳ bạt" required autoFocus />