- Reusable admin primitives: AdminTable, AdminPagination, AdminToolbar, AdminSearch/Select, StatusBadge + fa number/date/toman + useDebouncedValue. - Four board routes with search, filters, paging: orders (read-only), sellers (approve/reject/verify), users (ban/unban), products (active toggle). Wired into the admin sidebar nav + hooks. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
151 lines
4.5 KiB
TypeScript
151 lines
4.5 KiB
TypeScript
import type { MetaFunction } from "@remix-run/node";
|
||
import { useState } from "react";
|
||
import { useAdminOrders } from "~/requestHandler/use-admin-hooks";
|
||
import type { AdminOrderRow } from "~/requestHandler/use-admin-hooks";
|
||
import {
|
||
AdminTable,
|
||
AdminPagination,
|
||
AdminToolbar,
|
||
AdminSearch,
|
||
AdminSelect,
|
||
AdminPageTitle,
|
||
StatusBadge,
|
||
faToman,
|
||
faDate,
|
||
useDebouncedValue,
|
||
type Column,
|
||
} from "~/components/admin/DataTable";
|
||
|
||
export const meta: MetaFunction = () => [
|
||
{ title: "سفارشها | پنل مدیریت" },
|
||
{ name: "robots", content: "noindex, nofollow" },
|
||
];
|
||
|
||
const PAGE_SIZE = 25;
|
||
|
||
const STATUS: Record<string, { label: string; tone: "green" | "red" | "yellow" | "blue" | "gray" }> = {
|
||
pending: { label: "در انتظار", tone: "yellow" },
|
||
confirmed: { label: "تأییدشده", tone: "blue" },
|
||
shipped: { label: "ارسالشده", tone: "blue" },
|
||
delivered: { label: "تحویلشده", tone: "green" },
|
||
cancelled: { label: "لغوشده", tone: "red" },
|
||
};
|
||
const PAYMENT: Record<string, { label: string; tone: "green" | "red" | "yellow" }> = {
|
||
paid: { label: "پرداختشده", tone: "green" },
|
||
pending: { label: "در انتظار", tone: "yellow" },
|
||
failed: { label: "ناموفق", tone: "red" },
|
||
};
|
||
|
||
export default function AdminOrders() {
|
||
const [page, setPage] = useState(1);
|
||
const [search, setSearch] = useState("");
|
||
const [status, setStatus] = useState("");
|
||
const [payment, setPayment] = useState("");
|
||
const debouncedSearch = useDebouncedValue(search);
|
||
|
||
const { data, isLoading } = useAdminOrders({
|
||
page,
|
||
page_size: PAGE_SIZE,
|
||
search: debouncedSearch,
|
||
status,
|
||
payment_status: payment,
|
||
});
|
||
|
||
const reset = (fn: () => void) => {
|
||
fn();
|
||
setPage(1);
|
||
};
|
||
|
||
const columns: Column<AdminOrderRow>[] = [
|
||
{
|
||
header: "کد",
|
||
render: (o) => <span className="tabular-nums text-GRAY">{o.id.slice(0, 8)}</span>,
|
||
},
|
||
{
|
||
header: "خریدار",
|
||
render: (o) => (
|
||
<div className="min-w-[120px]">
|
||
<p className="font-semibold">{o.userName || "—"}</p>
|
||
<p className="text-GRAY text-[11px] tabular-nums" dir="ltr">
|
||
{o.userPhone || ""}
|
||
</p>
|
||
</div>
|
||
),
|
||
},
|
||
{
|
||
header: "فروشگاه",
|
||
render: (o) => <span>{o.sellerName || o.sellerUsername || "—"}</span>,
|
||
},
|
||
{
|
||
header: "مبلغ",
|
||
render: (o) => <span className="tabular-nums whitespace-nowrap">{faToman(o.totalPrice)}</span>,
|
||
},
|
||
{
|
||
header: "پرداخت",
|
||
render: (o) => {
|
||
const p = PAYMENT[o.paymentStatus] || { label: o.paymentStatus, tone: "gray" as const };
|
||
return <StatusBadge label={p.label} tone={p.tone} />;
|
||
},
|
||
},
|
||
{
|
||
header: "وضعیت",
|
||
render: (o) => {
|
||
const s = STATUS[o.status] || { label: o.status, tone: "gray" as const };
|
||
return <StatusBadge label={s.label} tone={s.tone} />;
|
||
},
|
||
},
|
||
{
|
||
header: "تاریخ",
|
||
render: (o) => <span className="text-GRAY whitespace-nowrap">{faDate(o.createdAt)}</span>,
|
||
},
|
||
];
|
||
|
||
return (
|
||
<div>
|
||
<AdminPageTitle>سفارشها</AdminPageTitle>
|
||
<AdminToolbar>
|
||
<AdminSearch
|
||
value={search}
|
||
onChange={(v) => reset(() => setSearch(v))}
|
||
placeholder="جستجوی خریدار یا فروشگاه…"
|
||
/>
|
||
<AdminSelect
|
||
value={status}
|
||
onChange={(v) => reset(() => setStatus(v))}
|
||
options={[
|
||
{ value: "", label: "همه وضعیتها" },
|
||
{ value: "pending", label: "در انتظار" },
|
||
{ value: "confirmed", label: "تأییدشده" },
|
||
{ value: "shipped", label: "ارسالشده" },
|
||
{ value: "delivered", label: "تحویلشده" },
|
||
{ value: "cancelled", label: "لغوشده" },
|
||
]}
|
||
/>
|
||
<AdminSelect
|
||
value={payment}
|
||
onChange={(v) => reset(() => setPayment(v))}
|
||
options={[
|
||
{ value: "", label: "همه پرداختها" },
|
||
{ value: "paid", label: "پرداختشده" },
|
||
{ value: "pending", label: "در انتظار" },
|
||
{ value: "failed", label: "ناموفق" },
|
||
]}
|
||
/>
|
||
</AdminToolbar>
|
||
|
||
<AdminTable
|
||
columns={columns}
|
||
rows={data?.results || []}
|
||
isLoading={isLoading}
|
||
getRowKey={(o) => o.id}
|
||
/>
|
||
<AdminPagination
|
||
page={page}
|
||
pageSize={PAGE_SIZE}
|
||
count={data?.count || 0}
|
||
onPageChange={setPage}
|
||
/>
|
||
</div>
|
||
);
|
||
}
|