فهرست سفارشها
پیشنمایش تمامصفحهجستوجو و فیلتر وضعیت، جدول با برچسب وضعیت و منوی عملیات هر ردیف، حالت خالی و صفحهبندی.
۱. نصب کامپوننتها
این بلاک از ۱۱ آیتم دیگ استفاده میکند: badgebuttoncontrol-sizedropdown-menuempty-stateiconspaginationpersiansearch-fieldselecttable
پکیجمنیجر پیشفرض Node.jsنصب سریع با لینک سخت و دیسک مشترکYarn نسخهٔ ۲ به بالا (Berry)رانتایم و پکیجمنیجر Bun
npx digdesign@latest add https://docs.digdesign.ir/r/badge.json https://docs.digdesign.ir/r/button.json https://docs.digdesign.ir/r/control-size.json https://docs.digdesign.ir/r/dropdown-menu.json https://docs.digdesign.ir/r/empty-state.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/pagination.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/search-field.json https://docs.digdesign.ir/r/select.json https://docs.digdesign.ir/r/table.jsonpnpm dlx digdesign@latest add https://docs.digdesign.ir/r/badge.json https://docs.digdesign.ir/r/button.json https://docs.digdesign.ir/r/control-size.json https://docs.digdesign.ir/r/dropdown-menu.json https://docs.digdesign.ir/r/empty-state.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/pagination.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/search-field.json https://docs.digdesign.ir/r/select.json https://docs.digdesign.ir/r/table.jsonyarn dlx digdesign@latest add https://docs.digdesign.ir/r/badge.json https://docs.digdesign.ir/r/button.json https://docs.digdesign.ir/r/control-size.json https://docs.digdesign.ir/r/dropdown-menu.json https://docs.digdesign.ir/r/empty-state.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/pagination.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/search-field.json https://docs.digdesign.ir/r/select.json https://docs.digdesign.ir/r/table.jsonbunx --bun digdesign@latest add https://docs.digdesign.ir/r/badge.json https://docs.digdesign.ir/r/button.json https://docs.digdesign.ir/r/control-size.json https://docs.digdesign.ir/r/dropdown-menu.json https://docs.digdesign.ir/r/empty-state.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/pagination.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/search-field.json https://docs.digdesign.ir/r/select.json https://docs.digdesign.ir/r/table.json۲. کد صفحه
کد را در app/<route>/page.tsx بگذارید و متنها، داده و هندلرها را با محصول خودتان عوض کنید.
orders-list.tsx
"use client";
import * as React from "react";
import {
CircleXIcon,
EyeIcon,
MoreHorizontalIcon,
ReceiptIcon,
} from "@/components/icons";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { ControlSizeProvider } from "@/components/ui/control-size";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import {
EmptyState,
EmptyStateActions,
EmptyStateDescription,
EmptyStateTitle,
} from "@/components/ui/empty-state";
import {
Pagination,
PaginationContent,
PaginationItem,
PaginationLink,
PaginationNext,
PaginationPrevious,
} from "@/components/ui/pagination";
import { SearchField } from "@/components/ui/search-field";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import { formatCurrency, formatJalali, toFaDigits } from "@/lib/persian";
type Status = "paid" | "pending" | "shipped" | "refunded";
const STATUS: Record<
Status,
{ text: string; color: "success" | "warning" | "info" | "destructive" }
> = {
paid: { text: "پرداختشده", color: "success" },
pending: { text: "در انتظار پرداخت", color: "warning" },
shipped: { text: "ارسالشده", color: "info" },
refunded: { text: "مرجوعی", color: "destructive" },
};
const ORDERS: {
id: string;
customer: string;
date: string;
amount: number;
status: Status;
}[] = [
{
id: "1042",
customer: "آرمیتا رستگار",
date: "2026-09-27",
amount: 2_450_000,
status: "paid",
},
{
id: "1041",
customer: "کیان مرادی",
date: "2026-09-27",
amount: 890_000,
status: "pending",
},
{
id: "1040",
customer: "نیلوفر احمدی",
date: "2026-09-26",
amount: 5_120_000,
status: "refunded",
},
{
id: "1039",
customer: "سینا کریمی",
date: "2026-09-25",
amount: 1_300_000,
status: "shipped",
},
];
const PAGE_COUNT = 5;
/** منوی عملیاتِ هر سفارش؛ هم در جدول و هم در کارتِ موبایل. */
function OrderActions({ id }: { id: string }) {
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
size="icon"
aria-label={`عملیات سفارش ${toFaDigits(id)}`}
>
<MoreHorizontalIcon />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem>
<EyeIcon />
مشاهدهٔ جزئیات
</DropdownMenuItem>
<DropdownMenuItem>
<ReceiptIcon />
چاپ فاکتور
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem variant="destructive">
<CircleXIcon />
لغو سفارش
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
}
export default function OrdersListBlock() {
const [query, setQuery] = React.useState("");
const [status, setStatus] = React.useState("all");
const [page, setPage] = React.useState(1);
const rows = ORDERS.filter(
(order) =>
(status === "all" || order.status === status) &&
(order.customer.includes(query) || order.id.includes(query)),
);
return (
<ControlSizeProvider size="xl" asChild>
<main className="mx-auto flex w-full min-w-0 max-w-5xl flex-col gap-6 p-4 sm:p-6">
<header className="mb-2 flex flex-col gap-1">
<h1 className="text-2xl font-bold">سفارشها</h1>
<p className="text-sm text-muted-foreground">
سفارشهای فروشگاه را بر اساس وضعیت پیگیری کنید.
</p>
</header>
<ControlSizeProvider size="xl">
{/* ابزارها ۳۲ پیکسل بالای جدول (mb-4 + gap-4)؛ جدا از جدول خوانده میشوند. */}
<div className="mb-4 flex flex-wrap items-center gap-2">
<SearchField
aria-label="جستوجوی سفارش"
placeholder="نام مشتری یا شمارهٔ سفارش"
value={query}
onValueChange={setQuery}
className="min-w-0 flex-1 basis-60"
/>
<Select
value={status}
onValueChange={(value) => setStatus(value as string)}
>
<SelectTrigger aria-label="فیلتر وضعیت" className="w-44">
<SelectValue placeholder="وضعیت" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">همهٔ وضعیتها</SelectItem>
{(Object.keys(STATUS) as Status[]).map((key) => (
<SelectItem key={key} value={key}>
{STATUS[key].text}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
{rows.length === 0 ? (
<EmptyState variant="bordered">
<EmptyStateTitle>سفارشی با این فیلتر پیدا نشد</EmptyStateTitle>
<EmptyStateDescription>
عبارت جستوجو یا وضعیت را تغییر دهید.
</EmptyStateDescription>
<EmptyStateActions>
<Button
variant="outline"
onClick={() => {
setQuery("");
setStatus("all");
}}
>
پاککردن فیلترها
</Button>
</EmptyStateActions>
</EmptyState>
) : (
<>
{/* موبایل: کارتِ هر سفارش با وضعیت و منوی عملیات؛ جدول از md به بعد. */}
<ul className="flex flex-col divide-y divide-border rounded-xl border border-border bg-card md:hidden">
{rows.map((order) => (
<li key={order.id} className="flex items-center gap-3 p-4">
<span className="flex min-w-0 flex-1 flex-col gap-1">
<span className="flex items-center gap-2">
<span className="font-bold">{order.customer}</span>
<Badge
variant="soft"
color={STATUS[order.status].color}
>
{STATUS[order.status].text}
</Badge>
</span>
<span className="text-sm text-muted-foreground tabular-nums">
#{toFaDigits(order.id)} · {formatJalali(order.date)} ·{" "}
{formatCurrency(order.amount)}
</span>
</span>
<OrderActions id={order.id} />
</li>
))}
</ul>
<div className="hidden overflow-hidden rounded-xl border border-border md:block">
<Table>
<TableHeader className="bg-muted">
<TableRow>
<TableHead>شماره</TableHead>
<TableHead>مشتری</TableHead>
<TableHead>تاریخ</TableHead>
<TableHead align="end">مبلغ</TableHead>
<TableHead>وضعیت</TableHead>
<TableHead align="end">
<span className="sr-only">عملیات</span>
</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{rows.map((order) => (
<TableRow key={order.id}>
<TableCell>{toFaDigits(order.id)}</TableCell>
<TableCell>{order.customer}</TableCell>
<TableCell>{formatJalali(order.date)}</TableCell>
<TableCell align="end">
{formatCurrency(order.amount)}
</TableCell>
<TableCell>
<Badge
variant="soft"
color={STATUS[order.status].color}
>
{STATUS[order.status].text}
</Badge>
</TableCell>
<TableCell align="end">
<OrderActions id={order.id} />
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
</>
)}
<Pagination>
<PaginationContent>
<PaginationItem>
<PaginationPrevious
href="#"
aria-disabled={page === 1}
onClick={() => setPage((value) => Math.max(1, value - 1))}
/>
</PaginationItem>
{Array.from({ length: PAGE_COUNT }, (_, index) => index + 1).map(
(number) => (
<PaginationItem key={number}>
<PaginationLink
href="#"
isActive={number === page}
onClick={() => setPage(number)}
>
{toFaDigits(number)}
</PaginationLink>
</PaginationItem>
),
)}
<PaginationItem>
<PaginationNext
href="#"
aria-disabled={page === PAGE_COUNT}
onClick={() =>
setPage((value) => Math.min(PAGE_COUNT, value + 1))
}
/>
</PaginationItem>
</PaginationContent>
</Pagination>
</ControlSizeProvider>
</main>
</ControlSizeProvider>
);
}