From db167062e6220f5b532af0ebf3bca2d6c3cb1f3d Mon Sep 17 00:00:00 2001 From: "soroush.asadi" Date: Wed, 3 Jun 2026 06:48:58 +0330 Subject: [PATCH] feat(admin): search + pagination on all data-driven tables - AdminResource: client-side search box (matches across all fields) + 25/page pagination with prev/next and a filtered-count footer - bumped pageSize on server-paged configs (users/blogs/comments/discounts/music/ fonts/tags) so search/paginate covers the full set Co-Authored-By: Claude Opus 4.8 --- src/components/admin/AdminResource.tsx | 41 +++++++++++++++++++++--- src/components/admin/admin-resources.tsx | 7 ++++ 2 files changed, 43 insertions(+), 5 deletions(-) diff --git a/src/components/admin/AdminResource.tsx b/src/components/admin/AdminResource.tsx index 392d9ae..9ca2784 100644 --- a/src/components/admin/AdminResource.tsx +++ b/src/components/admin/AdminResource.tsx @@ -43,6 +43,8 @@ const btn = "rounded-lg bg-indigo-600 px-3 py-1.5 text-xs font-medium text-white const btnGhost = "rounded-lg border border-[#262b40] px-3 py-1.5 text-xs text-gray-300 hover:bg-[#161a2e]"; const inputCls = "w-full rounded-lg border border-[#262b40] bg-[#0c0e1a] px-3 py-2 text-sm text-gray-100 outline-none focus:border-indigo-500"; +const PAGE_SIZE = 25; + export function AdminResource({ config }: { config: ResourceConfig }) { const idKey = config.idKey ?? "id"; const [rows, setRows] = useState[]>([]); @@ -51,6 +53,8 @@ export function AdminResource({ config }: { config: ResourceConfig }) { const [editing, setEditing] = useState | null>(null); const [creating, setCreating] = useState(false); const [form, setForm] = useState>({}); + const [query, setQuery] = useState(""); + const [page, setPage] = useState(1); const [saving, setSaving] = useState(false); const url = (suffix = "") => `/api/admin/resource/${config.basePath}${suffix}`; @@ -129,6 +133,13 @@ export function AdminResource({ config }: { config: ResourceConfig }) { } }; + // Client-side search (across all fields) + pagination. + const q = query.trim().toLowerCase(); + const filtered = q ? rows.filter((r) => JSON.stringify(r).toLowerCase().includes(q)) : rows; + const totalPages = Math.max(1, Math.ceil(filtered.length / PAGE_SIZE)); + const safePage = Math.min(page, totalPages); + const paged = filtered.slice((safePage - 1) * PAGE_SIZE, safePage * PAGE_SIZE); + return (
@@ -136,9 +147,16 @@ export function AdminResource({ config }: { config: ResourceConfig }) {

{config.title}

{config.description &&

{config.description}

}
- {config.canCreate && config.fields && ( - - )} +
+ { setQuery(e.target.value); setPage(1); }} + /> + {config.canCreate && config.fields && ( + + )} +
{error &&

{error}

} @@ -158,10 +176,10 @@ export function AdminResource({ config }: { config: ResourceConfig }) { {loading ? ( در حال بارگذاری… - ) : rows.length === 0 ? ( + ) : paged.length === 0 ? ( رکوردی یافت نشد. ) : ( - rows.map((row, i) => ( + paged.map((row, i) => ( {config.columns.map((c) => ( @@ -193,6 +211,19 @@ export function AdminResource({ config }: { config: ResourceConfig }) { + {!loading && filtered.length > 0 && ( +
+ {filtered.length.toLocaleString("fa-IR")} مورد{q ? " (فیلترشده)" : ""} + {totalPages > 1 && ( +
+ + صفحهٔ {safePage.toLocaleString("fa-IR")} از {totalPages.toLocaleString("fa-IR")} + +
+ )} +
+ )} + {(creating || editing) && config.fields && (
e.stopPropagation()}> diff --git a/src/components/admin/admin-resources.tsx b/src/components/admin/admin-resources.tsx index 95ee9b4..3625749 100644 --- a/src/components/admin/admin-resources.tsx +++ b/src/components/admin/admin-resources.tsx @@ -70,6 +70,7 @@ export const tagsConfig: ResourceConfig = { title: "برچسب‌ها", description: "برچسب‌های کلیدواژه برای قالب‌ها و محتوا.", basePath: "tags", + listQuery: "pageSize=500&page_size=500", listKey: "items", canCreate: true, canEdit: true, @@ -91,6 +92,7 @@ export const fontsConfig: ResourceConfig = { title: "فونت‌ها", description: "فونت‌های در دسترس در ویرایشگرهای استودیو.", basePath: "fonts", + listQuery: "pageSize=500&page_size=500", listKey: "items", canCreate: true, canEdit: true, @@ -115,6 +117,7 @@ export const musicConfig: ResourceConfig = { title: "موسیقی", description: "ترک‌های صوتی موجود در کتابخانهٔ موسیقی استودیو.", basePath: "music", + listQuery: "pageSize=500&page_size=500", listKey: "items", canCreate: true, canEdit: false, @@ -143,6 +146,7 @@ export const blogsConfig: ResourceConfig = { title: "مقالات بلاگ", description: "مقالات سیستم مدیریت محتوا (تولیدشده توسط هوش مصنوعی نیز).", basePath: "blogs", + listQuery: "pageSize=500&page_size=500", listKey: "items", canCreate: true, canEdit: true, @@ -213,6 +217,7 @@ export const commentsConfig: ResourceConfig = { title: "نظرات", description: "مدیریت نظرات کاربران روی مقالات و قالب‌ها.", basePath: "comments", + listQuery: "pageSize=500&page_size=500", listKey: "data", canCreate: false, canEdit: false, @@ -264,6 +269,7 @@ export const usersConfig: ResourceConfig = { title: "کاربران", description: "حساب‌های این مجموعه. مسدودسازی یا مدیریت در زیر.", basePath: "users", + listQuery: "pageSize=500&page_size=500", listKey: "data", columns: [ { key: "email", label: "ایمیل" }, @@ -298,6 +304,7 @@ export const discountsConfig: ResourceConfig = { title: "تخفیف‌ها", description: "کدهای تخفیف / کوپن. (کدها اینجا ساخته می‌شوند؛ هنوز API ویرایش/حذف وجود ندارد.)", basePath: "discounts", + listQuery: "pageSize=500&page_size=500", listKey: "data", canCreate: true, columns: [