"use client" import { useState, useEffect } from "react" 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 { BookOpen, Loader2, Plus, Upload, Edit, Trash2 } from "lucide-react" import { toast } from "sonner" import Link from "next/link" interface Novel { id: string title: string slug: string authorName: string status: string totalChapters: number } interface Genre { id: string name: string } export function NovelClient() { const [novels, setNovels] = useState([]) const [loading, setLoading] = useState(true) const [openAdd, setOpenAdd] = useState(false) const [submitting, setSubmitting] = useState(false) const [uploadingEpub, setUploadingEpub] = useState(false) // Form states const [title, setTitle] = useState("") const [authorName, setAuthorName] = useState("") const [description, setDescription] = useState("") const [status, setStatus] = useState("Đang ra") // Edit states const [openEdit, setOpenEdit] = useState(false) const [editingNovel, setEditingNovel] = useState(null) const [loadingEditData, setLoadingEditData] = useState(false) // Genre states const [genres, setGenres] = useState([]) const [selectedGenres, setSelectedGenres] = useState([]) const [newGenreName, setNewGenreName] = useState("") const [addingGenre, setAddingGenre] = useState(false) // Delete states const [openDelete, setOpenDelete] = useState(false) const [deletingNovelId, setDeletingNovelId] = useState(null) const fetchNovels = async () => { try { const res = await fetch("/api/mod/truyen") if (!res.ok) throw new Error("Lấy danh sách lỗi") const data = await res.json() setNovels(data) } catch { toast.error("Không thể tải danh sách truyện") } finally { setLoading(false) } } const fetchGenres = async () => { try { const res = await fetch("/api/mod/the-loai") if (res.ok) { const data = await res.json() setGenres(data) } } catch { console.error("Failed to fetch genres") } } useEffect(() => { fetchNovels() fetchGenres() }, []) const toggleGenre = (id: string) => { setSelectedGenres(prev => prev.includes(id) ? prev.filter(gId => gId !== id) : [...prev, id] ) } const handleAddGenre = async () => { if (!newGenreName.trim()) return setAddingGenre(true) try { const res = await fetch("/api/mod/the-loai", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name: newGenreName, description: "" }) }) const data = await res.json() if (!res.ok) throw new Error(data.error || "Thêm lỗi") toast.success("Thêm thể loại thành công") setNewGenreName("") fetchGenres() setSelectedGenres(prev => [...prev, data.id]) } catch (error: any) { toast.error(error.message) } finally { setAddingGenre(false) } } const handleDeleteGenre = async (id: string, name: string) => { if (!confirm(`Bạn có chắc muốn xóa thể loại "${name}" khỏi hệ thống?`)) return; try { const res = await fetch(`/api/mod/the-loai?id=${id}`, { method: "DELETE" }) if (!res.ok) { const data = await res.json() throw new Error(data.error || "Xóa lỗi") } toast.success("Đã xóa thể loại thành công") fetchGenres() // Clean up from selected lists setSelectedGenres(prev => prev.filter(gId => gId !== id)) } catch (error: any) { toast.error(error.message) } } const handleAddSubmit = async (e: React.FormEvent) => { e.preventDefault() if (!title || !authorName || !description) { toast.error("Vui lòng điền đầy đủ thông tin") return } setSubmitting(true) try { const res = await fetch("/api/mod/truyen", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ title, authorName, description, genreIds: selectedGenres }), // Can add status here later if API accepts it on create }) if (!res.ok) throw new Error("Thêm mới thất bại") toast.success("Đã thêm truyện thành công!") setOpenAdd(false) setTitle("") setAuthorName("") setDescription("") setStatus("Đang ra") setSelectedGenres([]) fetchNovels() } catch { toast.error("Lỗi khi thêm truyện mới") } finally { setSubmitting(false) } } const handleEpubUpload = async (e: React.ChangeEvent) => { const file = e.target.files?.[0] if (!file) return if (!file.name.endsWith('.epub')) { toast.error("Vui lòng chọn file định dạng .epub") e.target.value = "" // Reset input return } setUploadingEpub(true) const formData = new FormData() formData.append("file", file) try { const res = await fetch("/api/mod/epub", { method: "POST", body: formData, }) if (!res.ok) { const data = await res.json() throw new Error(data.error || "Lỗi khi tải lên EPUB") } toast.success("Phân tích và xuất bản EPUB thành công!") fetchNovels() } catch (err: any) { toast.error(err.message || "Có lỗi xảy ra khi xử lý file EPUB") } finally { setUploadingEpub(false) e.target.value = "" // Reset input } } const handleOpenEdit = async (novel: Novel) => { setEditingNovel(novel) setTitle(novel.title) setAuthorName(novel.authorName) setStatus(novel.status) setDescription("") setOpenEdit(true) setLoadingEditData(true) try { const res = await fetch(`/api/mod/truyen/${novel.id}`) if (res.ok) { const data = await res.json() setDescription(data.description || "") if (data.genres && Array.isArray(data.genres)) { setSelectedGenres(data.genres.map((g: any) => g.genreId)) } else { setSelectedGenres([]) } } else { toast.error("Không tải được chi tiết truyện") } } catch { toast.error("Không tải được chi tiết truyện") } finally { setLoadingEditData(false) } } const handleEditSubmit = async (e: React.FormEvent) => { e.preventDefault() if (!editingNovel || !title || !authorName) { toast.error("Vui lòng nhập tên truyện và tác giả") return } setSubmitting(true) try { const res = await fetch("/api/mod/truyen", { method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ id: editingNovel.id, title, authorName, description, genreIds: selectedGenres, status: status }), }) const data = await res.json() if (!res.ok) throw new Error(data.error || "Lỗi cập nhật") toast.success("Cập nhật truyện thành công!") setOpenEdit(false) fetchNovels() } catch (error: any) { toast.error(error.message) } finally { setSubmitting(false) } } const handleDeleteSubmit = async () => { if (!deletingNovelId) return setSubmitting(true) try { const res = await fetch(`/api/mod/truyen?id=${deletingNovelId}`, { 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 truyện thành công") setOpenDelete(false) fetchNovels() } catch (error: any) { toast.error(error.message) } finally { setSubmitting(false) } } return (

Quản lý truyện

{ setOpenAdd(val); if (val) { setTitle(""); setAuthorName(""); setDescription(""); setSelectedGenres([]); setNewGenreName(""); } }}> Thêm Truyện Mới Nhập thông tin cơ bản cho đầu truyện mới của bạn.
setTitle(e.target.value)} placeholder="Ví dụ: Phàm Nhân Tu Tiên" autoFocus />
setAuthorName(e.target.value)} placeholder="Ví dụ: Vong Ngữ" />
setNewGenreName(e.target.value)} className="flex-1" onKeyDown={(e) => { if (e.key === 'Enter') { e.preventDefault(); handleAddGenre(); } }} />
{genres.map(genre => (
toggleGenre(genre.id)}>{genre.name}
{ e.stopPropagation(); handleDeleteGenre(genre.id, genre.name); }} >
))} {genres.length === 0 && Chưa có thể loại nào}