diff --git a/app/mod/import/import-batch-client.tsx b/app/mod/import/import-batch-client.tsx index 1e618cb..908edab 100644 --- a/app/mod/import/import-batch-client.tsx +++ b/app/mod/import/import-batch-client.tsx @@ -186,6 +186,7 @@ export function ImportBatchClient() { formParse.append("preview", "true") formParse.append("splitMode", splitMode) if (splitMode === "regex") formParse.append("chapterRegex", chapterStartPattern) + formParse.append("enforceMaxChapters", "true") const r3 = await fetch("/api/mod/epub", { method: "POST", credentials: "include", body: formParse, signal }) const d3 = await r3.json().catch(() => ({})) @@ -199,7 +200,7 @@ export function ImportBatchClient() { } const chapterCount = Number(d3?.novel?.totalChapters ?? d3?.chapterCount ?? 0) - if (d3?.importBlocked === true || chapterCount > BATCH_IMPORT_MAX_CHAPTERS) { + if (d3?.importBlocked === true) { return { fileName: displayPath, ok: false, @@ -234,6 +235,7 @@ export function ImportBatchClient() { formImport.append("description", description) formImport.append("genreIds", genreIds.join(",")) formImport.append("replaceExisting", String(replaceExisting)) + formImport.append("enforceMaxChapters", "true") const r4 = await fetch("/api/mod/epub", { method: "POST", credentials: "include", body: formImport, signal }) const d4 = await r4.json().catch(() => ({})) @@ -367,8 +369,8 @@ export function ImportBatchClient() { Ghi đè nếu trùng tiêu đề (tắt = batch chỉ skip khi đã có truyện cùng tiêu đề)

- An toàn batch: tối đa {BATCH_IMPORT_MAX_CHAPTERS.toLocaleString()} chương sau khi tách mỗi file; quá{" "} - {Math.round(BATCH_IMPORT_MAX_MS_PER_FILE / 60000)} phút/file thì bỏ qua và xử lý file tiếp theo. + An toàn batch (chỉ khi import nhiều): tối đa {BATCH_IMPORT_MAX_CHAPTERS.toLocaleString()} chương sau khi tách mỗi file; quá{" "} + {Math.round(BATCH_IMPORT_MAX_MS_PER_FILE / 60000)} phút/file thì bỏ qua và xử lý file tiếp theo. Import một EPUB trên trang khác không bị giới hạn này.

diff --git a/app/mod/page.tsx b/app/mod/page.tsx index af5c45f..5bae978 100644 --- a/app/mod/page.tsx +++ b/app/mod/page.tsx @@ -11,7 +11,6 @@ export default async function ModDashboardPage() { let novelCount = 0 let totalViews = 0 let commentCount = 0 - let seriesCount = 0 try { const accessToken = (await cookies()).get(AUTH_COOKIE_NAME)?.value || "" @@ -24,7 +23,6 @@ export default async function ModDashboardPage() { novelCount = Number(data?.novelCount || 0) totalViews = Number(data?.totalViews || 0) commentCount = Number(data?.commentCount || 0) - seriesCount = Number(data?.seriesCount || 0) } } catch (error) { console.error("Failed to fetch mod overview", error) @@ -37,7 +35,7 @@ export default async function ModDashboardPage() { Chào mừng bạn đến với trang quản trị dành cho Moderator.

-
+

Tổng truyện

{novelCount}

@@ -50,10 +48,6 @@ export default async function ModDashboardPage() {

Bình luận mới

{commentCount}

-
-

Tổng series

-

{seriesCount}

-
) diff --git a/app/mod/series/page.tsx b/app/mod/series/page.tsx deleted file mode 100644 index cfe17db..0000000 --- a/app/mod/series/page.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import { requireModSessionUser } from "@/lib/server-auth" -import { SeriesClient } from "./series-client" - -export default async function ModSeriesPage() { - await requireModSessionUser() - - return -} diff --git a/app/mod/series/series-client.tsx b/app/mod/series/series-client.tsx deleted file mode 100644 index 075db1a..0000000 --- a/app/mod/series/series-client.tsx +++ /dev/null @@ -1,230 +0,0 @@ -"use client" - -import { useEffect, useMemo, useState } from "react" -import { Layers, Loader2, Pencil, Plus, Save, 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" - -interface SeriesItem { - id: string - name: string - slug: string - description?: string | null - _count?: { - novels: number - } -} - -export function SeriesClient() { - const [series, setSeries] = useState([]) - const [loading, setLoading] = useState(true) - const [submitting, setSubmitting] = useState(false) - const [name, setName] = useState("") - const [description, setDescription] = useState("") - const [editingId, setEditingId] = useState(null) - const [keyword, setKeyword] = useState("") - - const filteredSeries = useMemo(() => { - const q = keyword.trim().toLowerCase() - if (!q) return series - return series.filter((item) => { - return ( - item.name.toLowerCase().includes(q) || - item.slug.toLowerCase().includes(q) || - (item.description || "").toLowerCase().includes(q) - ) - }) - }, [keyword, series]) - - const fetchSeries = async () => { - try { - const res = await fetch("/api/mod/series") - if (!res.ok) throw new Error("Không thể tải danh sách series") - const data = await res.json() - setSeries(data) - } catch (error: any) { - toast.error(error.message || "Không thể tải danh sách series") - } finally { - setLoading(false) - } - } - - useEffect(() => { - fetchSeries() - }, []) - - const resetForm = () => { - setEditingId(null) - setName("") - setDescription("") - } - - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault() - if (!name.trim()) { - toast.error("Vui lòng nhập tên series") - return - } - - setSubmitting(true) - try { - const payload = { - id: editingId, - name: name.trim(), - description: description.trim(), - } - - const res = await fetch("/api/mod/series", { - 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.error || "Không thể lưu series") - } - - toast.success(editingId ? "Đã cập nhật series" : "Đã tạo series") - resetForm() - fetchSeries() - } catch (error: any) { - toast.error(error.message || "Không thể lưu series") - } finally { - setSubmitting(false) - } - } - - const handleEdit = (item: SeriesItem) => { - setEditingId(item.id) - setName(item.name) - setDescription(item.description || "") - } - - const handleDelete = async (id: string) => { - if (!confirm("Bạn chắc chắn muốn xóa series này?")) return - - setSubmitting(true) - try { - const res = await fetch(`/api/mod/series?id=${id}`, { method: "DELETE" }) - const data = await res.json() - if (!res.ok) { - throw new Error(data.error || "Không thể xóa series") - } - - toast.success("Đã xóa series") - if (editingId === id) resetForm() - fetchSeries() - } catch (error: any) { - toast.error(error.message || "Không thể xóa series") - } finally { - setSubmitting(false) - } - } - - return ( -
-
-

- Quản lý series -

- setKeyword(e.target.value)} - placeholder="Tìm series..." - className="max-w-xs" - /> -
- -
-
-

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

-
-
- - setName(e.target.value)} placeholder="Ví dụ: Overlord" /> -
-
- -