سفارشها روی بورد
پیشنمایش تمامصفحهسه ستونِ مرحله با شمار، کارتِ سفارش با مشتری، مبلغ و سن، برچسبِ «دیر شده» و دکمهٔ قدمِ بعد بهجای کشیدن.
۱. نصب کامپوننتها
این بلاک از ۵ آیتم دیگ استفاده میکند: avatarbuttoncontrol-sizeiconspersian
پکیجمنیجر پیشفرض Node.jsنصب سریع با لینک سخت و دیسک مشترکYarn نسخهٔ ۲ به بالا (Berry)رانتایم و پکیجمنیجر Bun
npx digdesign@latest add https://docs.digdesign.ir/r/avatar.json https://docs.digdesign.ir/r/button.json https://docs.digdesign.ir/r/control-size.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/persian.jsonpnpm dlx digdesign@latest add https://docs.digdesign.ir/r/avatar.json https://docs.digdesign.ir/r/button.json https://docs.digdesign.ir/r/control-size.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/persian.jsonyarn dlx digdesign@latest add https://docs.digdesign.ir/r/avatar.json https://docs.digdesign.ir/r/button.json https://docs.digdesign.ir/r/control-size.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/persian.jsonbunx --bun digdesign@latest add https://docs.digdesign.ir/r/avatar.json https://docs.digdesign.ir/r/button.json https://docs.digdesign.ir/r/control-size.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/persian.json۲. کد صفحه
کد را در app/<route>/page.tsx بگذارید و متنها، داده و هندلرها را با محصول خودتان عوض کنید.
orders-board.tsx
"use client"
import * as React from "react"
import { ChevronLeftIcon } from "@/components/icons"
import { Avatar } from "@/components/ui/avatar"
import { Button } from "@/components/ui/button"
import { ControlSizeProvider } from "@/components/ui/control-size"
import { formatCurrency, toFaDigits } from "@/lib/persian"
import { stockImages, type StockImageName } from "@/lib/stock-images"
type Stage = "new" | "packing" | "shipped"
const STAGES: { value: Stage; label: string; tone: string; next?: string }[] = [
{ value: "new", label: "تازه", tone: "orange", next: "شروعِ بستهبندی" },
{ value: "packing", label: "بستهبندی", tone: "blue", next: "تحویل به پیک" },
{ value: "shipped", label: "ارسالشده", tone: "green" },
]
const ORDERS: { id: string; customer: string; photo: StockImageName; total: number; age: string; stage: Stage; late?: boolean }[] = [
{ id: "1101", customer: "آرمیتا رستگار", photo: "personWoman1", total: 2_450_000, age: "۵ دقیقه", stage: "new" },
{ id: "1099", customer: "کیان مرادی", photo: "personMan1", total: 1_265_000, age: "۴۰ دقیقه", stage: "new", late: true },
{ id: "1097", customer: "نیلوفر احمدی", photo: "personWoman3", total: 5_100_000, age: "۲ ساعت", stage: "packing" },
{ id: "1095", customer: "سارا محمدی", photo: "personWoman2", total: 789_000, age: "دیروز", stage: "shipped" },
]
/**
* سفارشها روی بورد: سه ستونِ مرحله با شمار، کارتِ هر سفارش با مشتری، مبلغ و سنِ سفارش؛
* سفارشی که بیش از حد منتظر مانده برچسبِ «دیر شده» میگیرد و هر کارت دکمهٔ قدمِ بعد را دارد
* (بدونِ نیاز به کشیدن، که در لمسی و صفحهخوان سخت است).
*/
export default function OrdersBoardBlock() {
const [orders, setOrders] = React.useState(ORDERS)
const advance = (id: string) => setOrders((list) => list.map((order) => (order.id === id ? { ...order, stage: order.stage === "new" ? "packing" : "shipped", late: false } : order)))
return (
<ControlSizeProvider size="xl" asChild>
<main className="mx-auto flex w-full max-w-6xl flex-col gap-8 p-4 sm:p-6">
<header className="flex flex-col gap-1">
<h1 className="text-2xl font-bold">سفارشهای امروز</h1>
<p className="text-muted-foreground">{toFaDigits(orders.filter((order) => order.stage !== "shipped").length)} سفارش منتظرِ اقدام</p>
</header>
<div className="grid grid-cols-1 items-start gap-4 md:grid-cols-3">
{STAGES.map((stage) => {
const cards = orders.filter((order) => order.stage === stage.value)
return (
<section key={stage.value} aria-labelledby={`ob-${stage.value}`} className="flex flex-col gap-3 rounded-xl bg-subtle p-3">
<h2 id={`ob-${stage.value}`} className="flex items-center gap-2 px-1 pt-1 font-bold">
<span aria-hidden className="size-2.5 rounded-full" style={{ background: `var(--dig-${stage.tone}-500)` }} />
{stage.label}
<span className="text-sm font-normal text-muted-foreground tabular-nums">{toFaDigits(cards.length)}</span>
</h2>
{cards.length === 0 && <p className="px-1 py-6 text-center text-sm text-muted-foreground">سفارشی در این مرحله نیست.</p>}
{cards.map((order) => (
<article key={order.id} className="flex flex-col gap-3 rounded-lg border border-border bg-card p-3">
<div className="flex items-center gap-3">
<Avatar size="sm" alt="" src={stockImages[order.photo].src} />
<span className="flex flex-1 flex-col">
<span className="text-sm font-semibold">{order.customer}</span>
<span className="text-xs text-muted-foreground">
<bdi dir="ltr">#{toFaDigits(order.id)}</bdi> · {order.age} پیش
</span>
</span>
{order.late && <span className="rounded-md bg-[color-mix(in_oklab,var(--dig-orange-500)_15%,var(--card))] px-2 py-0.5 text-xs font-medium text-[var(--dig-orange-700)] dark:text-[var(--dig-orange-300)]">دیر شده</span>}
</div>
<div className="flex items-center justify-between gap-2">
<span className="text-sm font-bold tabular-nums">{formatCurrency(order.total)}</span>
{stage.next && (
<Button variant="outline" onClick={() => advance(order.id)}>
{stage.next}
<ChevronLeftIcon className="ltr:rotate-180" />
</Button>
)}
</div>
</article>
))}
</section>
)
})}
</div>
</main>
</ControlSizeProvider>
)
}