- hooks: useAdminDiscounts/Create/Update/Delete, useAdminCollections/Update/ Delete, AdminDiscountRow/AdminCollectionRow types, adminDelete helper. - /admin/discounts: table + create/edit modal form + delete + validity badge. - /admin/collections: table + is_public toggle + delete (moderation). - sidebar "تخفیفها" + "مجموعهها" nav links. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
154 lines
4.6 KiB
TypeScript
154 lines
4.6 KiB
TypeScript
import { useState } from "react";
|
||
import { Trash2, Eye, EyeOff } from "lucide-react";
|
||
import {
|
||
useAdminCollections,
|
||
useAdminCollectionUpdate,
|
||
useAdminCollectionDelete,
|
||
type AdminCollectionRow,
|
||
} from "~/requestHandler/use-admin-hooks";
|
||
import {
|
||
AdminTable,
|
||
AdminPagination,
|
||
AdminToolbar,
|
||
AdminSearch,
|
||
AdminSelect,
|
||
AdminPageTitle,
|
||
StatusBadge,
|
||
faNum,
|
||
faDate,
|
||
useDebouncedValue,
|
||
type Column,
|
||
} from "~/components/admin/DataTable";
|
||
|
||
const PAGE_SIZE = 25;
|
||
|
||
const CREATOR: Record<string, string> = {
|
||
seller: "فروشنده",
|
||
admin: "مدیر",
|
||
user: "کاربر",
|
||
};
|
||
|
||
export default function AdminCollections() {
|
||
const [page, setPage] = useState(1);
|
||
const [search, setSearch] = useState("");
|
||
const [creatorType, setCreatorType] = useState("");
|
||
const [isPublic, setIsPublic] = useState("");
|
||
const debouncedSearch = useDebouncedValue(search);
|
||
|
||
const { data, isLoading } = useAdminCollections({
|
||
page,
|
||
page_size: PAGE_SIZE,
|
||
search: debouncedSearch,
|
||
creator_type: creatorType,
|
||
is_public: isPublic,
|
||
});
|
||
|
||
const update = useAdminCollectionUpdate();
|
||
const del = useAdminCollectionDelete();
|
||
|
||
const reset = (fn: () => void) => {
|
||
fn();
|
||
setPage(1);
|
||
};
|
||
|
||
const toggle = (r: AdminCollectionRow) =>
|
||
update.mutate({ id: r.id, data: { isPublic: !r.isPublic } });
|
||
|
||
const onDelete = (r: AdminCollectionRow) => {
|
||
if (confirm(`حذف مجموعه «${r.title}»؟`)) del.mutate(r.id);
|
||
};
|
||
|
||
const columns: Column<AdminCollectionRow>[] = [
|
||
{
|
||
header: "عنوان",
|
||
render: (r) => (
|
||
<div className="flex items-center gap-2 min-w-0">
|
||
{r.coverImageUrl ? (
|
||
<img src={r.coverImageUrl} alt="" className="h-9 w-9 rounded-lg object-cover shrink-0" />
|
||
) : (
|
||
<div className="h-9 w-9 rounded-lg bg-WHITE3 shrink-0" />
|
||
)}
|
||
<p className="font-semibold truncate">{r.title}</p>
|
||
</div>
|
||
),
|
||
},
|
||
{ header: "سازنده", render: (r) => <span className="text-[12px]">{CREATOR[r.creatorType] || r.creatorType}</span> },
|
||
{
|
||
header: "محصولات",
|
||
render: (r) => <span className="tabular-nums">{faNum(r.productCount)}</span>,
|
||
},
|
||
{
|
||
header: "نمایش",
|
||
render: (r) =>
|
||
r.isPublic ? (
|
||
<StatusBadge label="عمومی" tone="green" />
|
||
) : (
|
||
<StatusBadge label="خصوصی" tone="gray" />
|
||
),
|
||
},
|
||
{ header: "تاریخ", render: (r) => <span className="text-GRAY whitespace-nowrap">{faDate(r.createdAt)}</span> },
|
||
{
|
||
header: "",
|
||
render: (r) => (
|
||
<div className="flex items-center gap-1 justify-end">
|
||
<button
|
||
onClick={() => toggle(r)}
|
||
className="grid h-8 w-8 place-items-center rounded-lg border border-WHITE3 hover:bg-WHITE2"
|
||
aria-label={r.isPublic ? "خصوصی کردن" : "عمومی کردن"}
|
||
title={r.isPublic ? "خصوصی کردن" : "عمومی کردن"}
|
||
>
|
||
{r.isPublic ? <EyeOff size={14} /> : <Eye size={14} />}
|
||
</button>
|
||
<button
|
||
onClick={() => onDelete(r)}
|
||
className="grid h-8 w-8 place-items-center rounded-lg border border-WHITE3 text-RED hover:bg-RED/5"
|
||
aria-label="حذف"
|
||
>
|
||
<Trash2 size={14} />
|
||
</button>
|
||
</div>
|
||
),
|
||
},
|
||
];
|
||
|
||
return (
|
||
<div>
|
||
<AdminPageTitle>مجموعهها</AdminPageTitle>
|
||
<AdminToolbar>
|
||
<AdminSearch
|
||
value={search}
|
||
onChange={(v) => reset(() => setSearch(v))}
|
||
placeholder="جستجوی عنوان / تلفن سازنده…"
|
||
/>
|
||
<AdminSelect
|
||
value={creatorType}
|
||
onChange={(v) => reset(() => setCreatorType(v))}
|
||
options={[
|
||
{ value: "", label: "همه سازندهها" },
|
||
{ value: "seller", label: "فروشنده" },
|
||
{ value: "admin", label: "مدیر" },
|
||
{ value: "user", label: "کاربر" },
|
||
]}
|
||
/>
|
||
<AdminSelect
|
||
value={isPublic}
|
||
onChange={(v) => reset(() => setIsPublic(v))}
|
||
options={[
|
||
{ value: "", label: "همه" },
|
||
{ value: "true", label: "عمومی" },
|
||
{ value: "false", label: "خصوصی" },
|
||
]}
|
||
/>
|
||
</AdminToolbar>
|
||
<AdminTable
|
||
columns={columns}
|
||
rows={data?.results || []}
|
||
isLoading={isLoading}
|
||
getRowKey={(r) => r.id}
|
||
emptyText="مجموعهای ثبت نشده است"
|
||
/>
|
||
<AdminPagination page={page} pageSize={PAGE_SIZE} count={data?.count || 0} onPageChange={setPage} />
|
||
</div>
|
||
);
|
||
}
|