Vitron-Front/app/routes/admin.collections.tsx
Arda Samadi c1118f70d6 admin: Slice 3b discounts & collections UI
- 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>
2026-07-25 14:48:50 +03:30

154 lines
4.6 KiB
TypeScript
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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>
);
}