سفارشها، نمای دوتکه
پیشنمایش تمامصفحهفهرستِ فشردهٔ سفارشها با جستوجو کنارِ جزئیاتِ سفارشِ انتخابشده: مشتری، نشانی، مراحل و دکمهٔ قدمِ بعد.
۱. نصب کامپوننتها
این بلاک از ۹ آیتم دیگ استفاده میکند: avatarbadgebuttoncontrol-sizeiconspersiansearch-fieldsteppertoast
پکیجمنیجر پیشفرض Node.jsنصب سریع با لینک سخت و دیسک مشترکYarn نسخهٔ ۲ به بالا (Berry)رانتایم و پکیجمنیجر Bun
npx digdesign@latest add https://docs.digdesign.ir/r/avatar.json 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/icons.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/search-field.json https://docs.digdesign.ir/r/stepper.json https://docs.digdesign.ir/r/toast.jsonpnpm dlx digdesign@latest add https://docs.digdesign.ir/r/avatar.json 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/icons.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/search-field.json https://docs.digdesign.ir/r/stepper.json https://docs.digdesign.ir/r/toast.jsonyarn dlx digdesign@latest add https://docs.digdesign.ir/r/avatar.json 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/icons.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/search-field.json https://docs.digdesign.ir/r/stepper.json https://docs.digdesign.ir/r/toast.jsonbunx --bun digdesign@latest add https://docs.digdesign.ir/r/avatar.json 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/icons.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/search-field.json https://docs.digdesign.ir/r/stepper.json https://docs.digdesign.ir/r/toast.json۲. کد صفحه
کد را در app/<route>/page.tsx بگذارید و متنها، داده و هندلرها را با محصول خودتان عوض کنید.
orders-split.tsx
"use client"
import * as React from "react"
import { FileTextIcon, PhoneIcon } from "@/components/icons"
import { Avatar } from "@/components/ui/avatar"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import { ControlSizeProvider } from "@/components/ui/control-size"
import { SearchField } from "@/components/ui/search-field"
import { Stepper, StepperItem, StepperTitle } from "@/components/ui/stepper"
import { toast } from "@/components/ui/toast"
import { formatCurrency, toFaDigits, toSearchKey } from "@/lib/persian"
import { stockImages, type StockImageName } from "@/lib/stock-images"
type Order = { id: string; customer: string; photo: StockImageName; total: number; items: number; time: string; step: number; address: string; mobile: string }
const ORDERS: Order[] = [
{ id: "1098", customer: "آرمیتا رستگار", photo: "personWoman1", total: 2_450_000, items: 2, time: "۱۰:۲۴", step: 1, address: "تهران، ولیعصر، کوچهٔ نسترن، پلاک ۱۲", mobile: "09121234567" },
{ id: "1097", customer: "کیان مرادی", photo: "personMan1", total: 1_265_000, items: 1, time: "۰۹:۵۰", step: 0, address: "اصفهان، چهارباغ، کوچهٔ نهم", mobile: "09131112233" },
{ id: "1096", customer: "نیلوفر احمدی", photo: "personWoman3", total: 5_100_000, items: 4, time: "دیروز", step: 2, address: "شیراز، بلوار چمران، پلاک ۸", mobile: "09171234567" },
]
const STEPS = ["تأیید", "آمادهسازی", "ارسال", "تحویل"]
const STATUS = ["در انتظارِ تأیید", "در حالِ آمادهسازی", "ارسال شد", "تحویل شد"]
/**
* سفارشها با نمای دوتکه: فهرستِ فشرده با جستوجو در یک ستون و جزئیاتِ سفارشِ انتخابشده در
* ستونِ دیگر (مشتری، نشانی، مراحل، اقدامِ بعدی)؛ مدیر بدونِ رفتوبرگشت سفارشها را جلو میبرد.
*/
export default function OrdersSplitBlock() {
const [orders, setOrders] = React.useState(ORDERS)
const [query, setQuery] = React.useState("")
const [activeId, setActiveId] = React.useState(ORDERS[0].id)
const visible = orders.filter((order) => toSearchKey(order.customer + order.id).includes(toSearchKey(query)))
const active = orders.find((order) => order.id === activeId) ?? orders[0]
const next = ["تأییدِ سفارش", "ارسال شد", "تحویل شد"][active.step]
return (
<ControlSizeProvider size="xl" asChild>
<main className="mx-auto grid w-full max-w-6xl grid-cols-1 items-start gap-6 p-4 sm:p-6 lg:grid-cols-[22rem_minmax(0,1fr)]">
<section aria-label="فهرستِ سفارشها" className="flex flex-col gap-3">
<h1 className="text-2xl font-bold">سفارشها</h1>
<SearchField aria-label="جستوجوی سفارش" placeholder="نامِ مشتری یا شماره" value={query} onValueChange={setQuery} />
<ul className="flex flex-col gap-2">
{visible.map((order) => (
<li key={order.id}>
<Button
variant="outline"
aria-current={order.id === active.id ? "true" : undefined}
onClick={() => setActiveId(order.id)}
className={`h-auto w-full justify-start gap-3 p-3 text-start font-normal ${order.id === active.id ? "border-primary bg-primary-subtle hover:bg-primary-subtle" : ""}`}
>
<Avatar alt="" src={stockImages[order.photo].src} />
<span className="flex min-w-0 flex-1 flex-col">
<span className="flex justify-between gap-2">
<span className="truncate font-semibold">{order.customer}</span>
<span className="text-xs text-muted-foreground">{order.time}</span>
</span>
<span className="flex justify-between gap-2 text-sm text-muted-foreground">
<bdi dir="ltr">#{toFaDigits(order.id)}</bdi>
<span className="tabular-nums">{formatCurrency(order.total)}</span>
</span>
</span>
</Button>
</li>
))}
</ul>
</section>
<section aria-label={`سفارشِ ${active.id}`} className="flex flex-col gap-6 rounded-xl border border-border bg-card p-6">
<div className="flex flex-wrap items-start justify-between gap-4">
<div className="flex flex-col gap-1">
<h2 className="text-xl font-bold">
سفارشِ <bdi dir="ltr">#{toFaDigits(active.id)}</bdi>
</h2>
<Badge variant="soft" color={active.step === 0 ? "warning" : active.step === 3 ? "success" : "info"} className="self-start">
{STATUS[active.step]}
</Badge>
</div>
<Button variant="outline">
<FileTextIcon />
چاپِ برچسب
</Button>
</div>
<Stepper value={active.step}>
{STEPS.map((step) => (
<StepperItem key={step}>
<StepperTitle>{step}</StepperTitle>
</StepperItem>
))}
</Stepper>
<dl className="grid grid-cols-1 gap-4 rounded-xl bg-subtle p-4 text-sm sm:grid-cols-2">
<div className="flex flex-col gap-1">
<dt className="text-muted-foreground">مشتری</dt>
<dd className="flex items-center gap-2 font-medium">
{active.customer}
<a href={`tel:${active.mobile}`} className="text-muted-foreground hover:text-foreground" aria-label={`تماس با ${active.customer}`}>
<PhoneIcon className="size-4" />
</a>
</dd>
</div>
<div className="flex flex-col gap-1">
<dt className="text-muted-foreground">مبلغ</dt>
<dd className="font-medium tabular-nums">
{formatCurrency(active.total)} · {toFaDigits(active.items)} قلم
</dd>
</div>
<div className="flex flex-col gap-1 sm:col-span-2">
<dt className="text-muted-foreground">نشانی</dt>
<dd>{active.address}</dd>
</div>
</dl>
{next && (
<div className="flex flex-wrap items-center justify-between gap-3 border-t border-border pt-6">
<span className="text-sm text-muted-foreground">قدمِ بعدی برای این سفارش</span>
<Button
onClick={() => {
setOrders((list) => list.map((order) => (order.id === active.id ? { ...order, step: order.step + 1 } : order)))
toast.success(`سفارشِ ${toFaDigits(active.id)}: ${next}`)
}}
>
{next}
</Button>
</div>
)}
</section>
</main>
</ControlSizeProvider>
)
}