مدیریتِ یک سفارش
پیشنمایش تمامصفحهنمای فروشنده: شماره با وضعیتِ پرداخت و ارسال و سفارشِ قبلی/بعدی، مسیرِ چهارمرحلهای با اقدامِ اصلیِ هر مرحله (ساختِ برچسبِ ارسال)، کالاها و ریزِ پرداخت، فعالیتها با یادداشتِ داخلی، و ستونِ یادداشتِ مشتری، مشتری، نقشه و تماس.
۱. نصب کامپوننتها
این بلاک از ۹ آیتم دیگ استفاده میکند: avatarbadgebuttoncardcontrol-sizeiconspersiantextareatoast
پکیجمنیجر پیشفرض 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/card.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/textarea.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/card.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/textarea.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/card.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/textarea.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/card.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/textarea.json https://docs.digdesign.ir/r/toast.json۲. کد صفحه
کد را در app/<route>/page.tsx بگذارید و متنها، داده و هندلرها را با محصول خودتان عوض کنید.
orders-manage.tsx
"use client"
import * as React from "react"
import { ChatIcon, CheckIcon, ChevronLeftIcon, ChevronRightIcon, EditIcon, EnvelopeIcon, FileTextIcon, PhoneIcon, TruckIcon } from "@/components/icons"
import { Avatar } from "@/components/ui/avatar"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import { Card, CardAction, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { ControlSizeProvider } from "@/components/ui/control-size"
import { Textarea } from "@/components/ui/textarea"
import { toast } from "@/components/ui/toast"
import { formatCurrency, toFaDigits } from "@/lib/persian"
import { stockImages, type StockImageName } from "@/lib/stock-images"
const STEPS = ["بررسیِ سفارش", "آمادهسازی", "ارسال", "تحویل"]
const ITEMS: { id: string; title: string; sku: string; variant: string; qty: number; price: number; image: StockImageName }[] = [
{ id: "1", title: "گلدانِ سرامیکی دوقلو", sku: "VAS-1000", variant: "سفید · مات", qty: 1, price: 990_000, image: "productVase" },
{ id: "2", title: "دفترِ یادداشتِ جلدسخت", sku: "NTB-5006", variant: "A5 · خطدار", qty: 2, price: 120_000, image: "coverNotebook" },
]
const SUBTOTAL = ITEMS.reduce((sum, item) => sum + item.qty * item.price, 0)
type Activity = { id: string; icon: React.ReactNode; title: string; time: string; note?: string; tone: "default" | "note" }
const INITIAL_ACTIVITY: Activity[] = [
{ id: "a1", icon: <CheckIcon />, title: "پرداخت تأیید شد", time: "امروز، ۱۰:۱۳", tone: "default" },
{ id: "a2", icon: <EnvelopeIcon />, title: "رسیدِ سفارش برای مشتری ایمیل شد", time: "امروز، ۱۰:۱۴", tone: "default" },
{ id: "a3", icon: <FileTextIcon />, title: "یادداشتِ آرمیتا رستگار", time: "امروز، ۱۰:۳۰", note: "مشتری خواسته بسته کادوپیچ شود؛ کارت تبریک داخلش نگذاریم.", tone: "note" },
]
/**
* جزئیاتِ سفارش برای فروشنده (پنلِ مدیریت): شمارهٔ سفارش با وضعیتِ پرداخت و ارسال، تاریخ و منبع، رفتن
* به سفارشِ قبلی/بعدی؛ مسیرِ چهارمرحلهای با «ساختِ برچسبِ ارسال» که سفارش را یک قدم جلو میبرد و لغو
* کنارش؛ کالاها و ریزِ پرداخت؛ گزارشِ فعالیت با افزودنِ یادداشت؛ و ستونِ کنار با یادداشتِ مشتری، مشتری
* با پیام، نشانی با نقشه و اطلاعاتِ تماس.
*/
export default function OrdersManageBlock() {
const [step, setStep] = React.useState(0)
const [activity, setActivity] = React.useState(INITIAL_ACTIVITY)
const [note, setNote] = React.useState("")
const fulfilled = step >= 2
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-wrap items-start justify-between gap-x-4 gap-y-4">
<div className="flex flex-col gap-2">
<div className="flex flex-wrap items-center gap-2">
<h1 className="text-2xl leading-snug font-black">
سفارشِ <bdi className="tabular-nums">{toFaDigits(12567)}</bdi>
</h1>
<Badge variant="soft" color="success">
پرداختشده
</Badge>
<Badge variant="soft" color={fulfilled ? "info" : "warning"}>
{fulfilled ? "ارسالشده" : "در انتظارِ ارسال"}
</Badge>
</div>
<p className="text-sm text-muted-foreground">۱۹ مهر ۱۴۰۵ · از فروشگاهِ آنلاین · مشتری: آرمیتا رستگار</p>
</div>
<div className="flex items-center gap-2">
<span className="text-sm text-muted-foreground tabular-nums">
{toFaDigits(12)} از {toFaDigits(320)}
</span>
<Button variant="outline" size="icon" aria-label="سفارشِ قبلی">
<ChevronRightIcon />
</Button>
<Button variant="outline" size="icon" aria-label="سفارشِ بعدی">
<ChevronLeftIcon />
</Button>
</div>
</header>
<div className="grid grid-cols-1 items-start gap-8 lg:grid-cols-[minmax(0,1fr)_20rem]">
<div className="flex min-w-0 flex-col gap-6">
{/* مسیرِ سفارش و اقدامِ اصلیِ مرحلهٔ فعلی. */}
<Card className="gap-5">
<CardContent className="flex flex-col gap-5">
<div className="flex flex-wrap items-center justify-between gap-2 rounded-lg bg-subtle px-4 py-2.5 text-sm">
<span className="text-muted-foreground">
ارسال از <span className="font-semibold text-foreground">انبارِ تهران</span>
</span>
<span className="text-muted-foreground">
تحویلِ تخمینی <span className="font-semibold text-foreground">۲۲ تا ۲۴ مهر</span>
</span>
</div>
<ol className="grid grid-cols-4 gap-2" aria-label="مراحلِ سفارش">
{STEPS.map((title, index) => (
<li key={title} aria-current={index === step ? "step" : undefined} className="flex flex-col gap-2">
<span className={`h-1.5 rounded-full ${index < step ? "bg-success" : index === step ? "bg-primary" : "bg-muted"}`} />
<span className={`text-xs sm:text-sm ${index === step ? "font-bold" : index < step ? "font-medium" : "text-muted-foreground"}`}>{title}</span>
</li>
))}
</ol>
<div className="flex flex-col-reverse gap-3 border-t border-border pt-5 sm:flex-row sm:items-center sm:justify-between">
<Button variant="ghost" className="text-destructive hover:text-destructive" disabled={step >= 3}>
لغوِ سفارش
</Button>
<Button
disabled={step >= 3}
onClick={() => {
const next = Math.min(step + 1, 3)
setStep(next)
setActivity((list) => [{ id: `s${next}`, icon: <TruckIcon />, title: `سفارش به «${STEPS[next]}» رفت`, time: "همین حالا", tone: "default" }, ...list])
toast.success(`سفارش به «${STEPS[next]}» رفت.`)
}}
>
<TruckIcon />
{step === 0 ? "تأیید و آمادهسازی" : step === 1 ? "ساختِ برچسبِ ارسال" : step === 2 ? "ثبتِ تحویل" : "تحویل شد"}
</Button>
</div>
</CardContent>
</Card>
<Card className="gap-0 py-0">
<CardHeader className="py-5">
<CardTitle className="text-base">کالاها</CardTitle>
<CardAction>
<span className="text-sm text-muted-foreground">{fulfilled ? "ارسالشده" : "ارسالنشده"}</span>
</CardAction>
</CardHeader>
<ul className="flex flex-col divide-y divide-border border-t border-border">
{ITEMS.map((item) => (
<li key={item.id} className="flex gap-4 px-6 py-4">
{/* eslint-disable-next-line @next/next/no-img-element -- عکس از کتابخانهٔ تصویرِ دیگ */}
<img src={stockImages[item.image].src} alt="" className="size-16 shrink-0 rounded-lg object-cover" />
<span className="flex min-w-0 flex-1 flex-col gap-0.5">
<span className="font-semibold">{item.title}</span>
<span className="text-sm text-muted-foreground">
<bdi dir="ltr">{item.sku}</bdi> · {item.variant} · {toFaDigits(item.qty)} عدد
</span>
</span>
<span className="shrink-0 font-bold tabular-nums">{formatCurrency(item.qty * item.price)}</span>
</li>
))}
</ul>
<dl className="flex flex-col gap-2 border-t border-border bg-subtle px-6 py-5 text-sm">
<div className="flex justify-between gap-4">
<dt className="text-muted-foreground">جمعِ {toFaDigits(ITEMS.length)} قلم</dt>
<dd className="tabular-nums">{formatCurrency(SUBTOTAL)}</dd>
</div>
<div className="flex justify-between gap-4">
<dt className="text-muted-foreground">ارسال</dt>
<dd>رایگان (انتخابِ مشتری)</dd>
</div>
<div className="flex justify-between gap-4">
<dt className="text-muted-foreground">روشِ پرداخت</dt>
<dd>درگاهِ بانکی · کارتِ ۶۰۳۷</dd>
</div>
<div className="flex justify-between gap-4 border-t border-border pt-3 text-base">
<dt className="font-bold">پرداختشده</dt>
<dd className="font-black tabular-nums">{formatCurrency(SUBTOTAL)}</dd>
</div>
</dl>
</Card>
<section aria-labelledby="activity-title" className="flex flex-col gap-4">
<h2 id="activity-title" className="text-lg font-bold">
فعالیتها
</h2>
<form
className="flex flex-col gap-3"
onSubmit={(event) => {
event.preventDefault()
if (!note.trim()) return
setActivity((list) => [{ id: `n${Date.now()}`, icon: <FileTextIcon />, title: "یادداشتِ شما", time: "همین حالا", note: note.trim(), tone: "note" }, ...list])
setNote("")
}}
>
<Textarea aria-label="یادداشتِ داخلی" placeholder="یادداشتی برای تیم بنویس؛ مشتری آن را نمیبیند." value={note} onChange={(event) => setNote(event.target.value)} rows={2} />
<Button type="submit" variant="outline" className="self-end" disabled={!note.trim()}>
افزودنِ یادداشت
</Button>
</form>
<ol className="flex flex-col">
{activity.map((item, index) => (
<li key={item.id} className="relative flex gap-3 pb-5 last:pb-0">
{index < activity.length - 1 && <span aria-hidden className="absolute start-5 top-11 bottom-0 w-px bg-border" />}
<span aria-hidden className="flex size-10 shrink-0 items-center justify-center rounded-full bg-muted [&>svg]:size-4">
{item.icon}
</span>
<span className="flex min-w-0 flex-1 flex-col gap-1.5 pt-1">
<span className="flex flex-wrap items-baseline justify-between gap-2">
<span className="font-semibold">{item.title}</span>
<span className="text-xs text-muted-foreground">{item.time}</span>
</span>
{item.note && <span className="rounded-lg bg-[color-mix(in_oklab,var(--dig-amber-400)_16%,var(--card))] p-3 text-sm leading-6">{item.note}</span>}
</span>
</li>
))}
</ol>
</section>
</div>
<aside className="flex flex-col gap-4">
<Card className="gap-3 bg-[color-mix(in_oklab,var(--dig-amber-400)_10%,var(--card))]">
<CardHeader>
<CardTitle className="text-base">یادداشتِ مشتری</CardTitle>
<CardAction>
<Button variant="ghost" size="icon" aria-label="ویرایشِ یادداشت" className="-me-2">
<EditIcon />
</Button>
</CardAction>
</CardHeader>
<CardContent className="text-sm leading-6">لطفاً کادوپیچ کنید؛ برای تولدِ مادرم است.</CardContent>
</Card>
<Card className="gap-4">
<CardHeader>
<CardTitle className="text-base">مشتری</CardTitle>
</CardHeader>
<CardContent className="flex flex-col gap-5">
<div className="flex items-center gap-3">
<Avatar alt="آرمیتا رستگار" src={stockImages.personWoman1.src} className="size-11" />
<span className="flex flex-1 flex-col">
<span className="font-bold">آرمیتا رستگار</span>
<span className="text-sm text-muted-foreground">{toFaDigits(4)} سفارشِ قبلی</span>
</span>
<Button variant="outline" size="icon" aria-label="پیام به مشتری">
<ChatIcon />
</Button>
</div>
<div className="flex flex-col gap-3 border-t border-border pt-5">
<span className="flex items-center justify-between">
<span className="text-sm font-semibold">نشانیِ ارسال</span>
<Button variant="ghost" size="icon" aria-label="ویرایشِ نشانی" className="-me-2">
<EditIcon />
</Button>
</span>
{/* eslint-disable-next-line @next/next/no-img-element -- نقشهٔ تزئینی؛ در محصول نقشهٔ واقعی میآید */}
<img src="/blocks/contact-map.svg" alt="نقشهٔ نشانیِ ارسال" className="aspect-[2/1] w-full rounded-lg object-cover dark:brightness-[0.8]" />
<p className="text-sm leading-6">تهران، خیابانِ ولیعصر، کوچهٔ نسترن، پلاکِ ۱۲، واحدِ ۴</p>
</div>
<div className="flex flex-col gap-2 border-t border-border pt-5">
<span className="text-sm font-semibold">تماس</span>
<a href="mailto:armita@dig.ir" className="flex items-center gap-2 text-sm hover:underline [&>svg]:size-4 [&>svg]:text-muted-foreground">
<EnvelopeIcon aria-hidden />
<bdi dir="ltr">armita@dig.ir</bdi>
</a>
<a href="tel:09123456789" className="flex items-center gap-2 text-sm hover:underline [&>svg]:size-4 [&>svg]:text-muted-foreground">
<PhoneIcon aria-hidden />
<bdi dir="ltr">۰۹۱۲ ۳۴۵ ۶۷۸۹</bdi>
</a>
</div>
</CardContent>
</Card>
</aside>
</div>
</main>
</ControlSizeProvider>
)
}