"use client" import { useEffect, useMemo, useState } from "react" import { GitMerge, Loader2, Pencil, Plus, Save, Tag, Trash2, X } from "lucide-react" import { toast } from "sonner" 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, } from "@/components/ui/dialog" interface Genre { id: string name: string slug: string description?: string | null icon?: string | null novelCount?: number } export function GenreClient() { const [genres, setGenres] = useState([]) const [loading, setLoading] = useState(true) const [submitting, setSubmitting] = useState(false) const [keyword, setKeyword] = useState("") // Form state const [editingId, setEditingId] = useState(null) const [formName, setFormName] = useState("") const [formDesc, setFormDesc] = useState("") const [formIcon, setFormIcon] = useState("") // Merge dialog const [mergeOpen, setMergeOpen] = useState(false) const [mergeSource, setMergeSource] = useState(null) const [mergeTargetId, setMergeTargetId] = useState("") const [mergeKeyword, setMergeKeyword] = useState("") // Delete confirm const [deleteTarget, setDeleteTarget] = useState(null) const [deleteOpen, setDeleteOpen] = useState(false) const filtered = useMemo(() => { const q = keyword.trim().toLowerCase() if (!q) return genres return genres.filter( (g) => g.name.toLowerCase().includes(q) || g.slug.toLowerCase().includes(q) ) }, [keyword, genres]) const mergeTargetOptions = useMemo(() => { const q = mergeKeyword.trim().toLowerCase() return genres.filter( (g) => g.id !== mergeSource?.id && (!q || g.name.toLowerCase().includes(q) || g.slug.toLowerCase().includes(q)) ) }, [genres, mergeSource, mergeKeyword]) const fetchGenres = async () => { try { const res = await fetch("/api/mod/the-loai") if (!res.ok) throw new Error("Không thể tải danh sách thể loại") const data: Genre[] = await res.json() // Enrich with novelCount from public endpoint const countRes = await fetch("/api/genres") if (countRes.ok) { const countData: Genre[] = await countRes.json() const countMap = Object.fromEntries(countData.map((g) => [g.id, g.novelCount ?? 0])) data.forEach((g) => { g.novelCount = countMap[g.id] ?? 0 }) } setGenres(data) } catch (err: any) { toast.error(err.message || "Lỗi tải thể loại") } finally { setLoading(false) } } useEffect(() => { fetchGenres() }, []) const resetForm = () => { setEditingId(null) setFormName("") setFormDesc("") setFormIcon("") } const handleEdit = (g: Genre) => { setEditingId(g.id) setFormName(g.name) setFormDesc(g.description || "") setFormIcon(g.icon || "") window.scrollTo({ top: 0, behavior: "smooth" }) } const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() if (!formName.trim()) { toast.error("Vui lòng nhập tên thể loại"); return } setSubmitting(true) try { const payload: Record = { name: formName.trim(), description: formDesc.trim() || null, icon: formIcon.trim() || null, } if (editingId) payload.id = editingId const res = await fetch("/api/mod/the-loai", { method: editingId ? "PUT" : "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload), }) const data = await res.json() if (!res.ok) throw new Error(data.detail || "Không thể lưu") toast.success(editingId ? "Đã cập nhật thể loại" : "Đã tạo thể loại") resetForm() fetchGenres() } catch (err: any) { toast.error(err.message) } finally { setSubmitting(false) } } const confirmDelete = (g: Genre) => { setDeleteTarget(g) setDeleteOpen(true) } const handleDelete = async () => { if (!deleteTarget) return setSubmitting(true) try { const res = await fetch(`/api/mod/the-loai?id=${deleteTarget.id}`, { method: "DELETE" }) const data = await res.json() if (!res.ok) throw new Error(data.detail || "Không thể xóa") toast.success(`Đã xóa thể loại "${deleteTarget.name}"`) if (editingId === deleteTarget.id) resetForm() setDeleteOpen(false) fetchGenres() } catch (err: any) { toast.error(err.message) } finally { setSubmitting(false) } } const openMerge = (g: Genre) => { setMergeSource(g) setMergeTargetId("") setMergeKeyword("") setMergeOpen(true) } const handleMerge = async () => { if (!mergeSource || !mergeTargetId) { toast.error("Vui lòng chọn thể loại đích"); return } setSubmitting(true) try { const res = await fetch("/api/mod/the-loai/merge", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ sourceId: mergeSource.id, targetId: mergeTargetId }), }) const data = await res.json() if (!res.ok) throw new Error(data.detail || "Không thể gộp") const target = genres.find((g) => g.id === mergeTargetId) toast.success(`Đã gộp "${mergeSource.name}" vào "${target?.name}"`) setMergeOpen(false) if (editingId === mergeSource.id) resetForm() fetchGenres() } catch (err: any) { toast.error(err.message) } finally { setSubmitting(false) } } return (

Quản lý Thể Loại

{/* Form tạo / chỉnh sửa */}

{editingId ? "Chỉnh sửa thể loại" : "Tạo thể loại mới"}

setFormName(e.target.value)} placeholder="Ví dụ: Kiếm Hiệp" />
setFormIcon(e.target.value)} placeholder="Ví dụ: Sword, Flame, Heart..." />