Vitron-Front/app/routes/admin.orders.tsx
Arda Samadi 935c337df0 feat(admin): monitoring boards UI — orders/sellers/users/products (slice 1)
- 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>
2026-07-25 12:17:22 +03:30

151 lines
4.5 KiB
TypeScript
Raw 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 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>
);
}