بلاک‌ها · فروشگاهی

هر ارسالِ سفارش یک کارت: شماره با رفتن به جزئیات، نوارِ چهارتکه‌ای مراحل، مبدأ و سرویس و وزن و تاریخ، و پانویسِ حمل‌کننده با وضعیت، کدِ رهگیریِ قابلِ کپی و زمانِ رسیدن.

۱. نصب کامپوننت‌ها

این بلاک از ۶ آیتم دیگ استفاده می‌کند: badgebuttoncontrol-sizeiconspersiantoast

پکیج‌منیجر پیش‌فرض 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/icons.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/toast.json
pnpm 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/icons.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/toast.json
yarn 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/icons.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/toast.json
bunx --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/icons.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/toast.json

۲. کد صفحه

کد را در app/<route>/page.tsx بگذارید و متن‌ها، داده و هندلرها را با محصول خودتان عوض کنید.

order-tracking-fulfillment.tsx
"use client"

import * as React from "react"

import { CheckIcon, ChevronLeftIcon, ClockIcon, CopyIcon, TruckIcon } from "@/components/icons"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import { ControlSizeProvider } from "@/components/ui/control-size"
import { toast } from "@/components/ui/toast"
import { toFaDigits } from "@/lib/persian"

const STAGES = ["آماده‌سازی", "مسیریابی", "تحویل به حمل", "تحویل به شما"]
type Fulfillment = { id: string; stage: number; progress: number; carrier: string; carrierTone: string; tracking: string; eta: string; rows: [string, string][] }
const FULFILLMENTS: Fulfillment[] = [
  {
    id: "۰۰۲۵۲",
    stage: 3,
    progress: 35,
    carrier: "پستِ پیشتاز",
    carrierTone: "bg-[var(--dig-red-600)]",
    tracking: "236597455501263",
    eta: "۲۳ مهر",
    rows: [
      ["مبدأ", "انبارِ تهران"],
      ["سرویس", "پیشتاز، ۲ تا ۳ روز"],
      ["وزن", "۱٫۸ کیلوگرم"],
      ["تاریخِ ارسال", "۲۱ مهر ۱۴۰۵"],
    ],
  },
  {
    id: "۰۰۲۵۳",
    stage: 4,
    progress: 100,
    carrier: "تیپاکس",
    carrierTone: "bg-[var(--dig-orange-500)]",
    tracking: "TPX-88213045",
    eta: "تحویل شد، ۲۰ مهر",
    rows: [
      ["مبدأ", "انبارِ اصفهان"],
      ["سرویس", "اکسپرس، ۱ روز"],
      ["وزن", "۰٫۶ کیلوگرم"],
      ["تاریخِ ارسال", "۱۹ مهر ۱۴۰۵"],
    ],
  },
]

/** نوارِ چهارتکه‌ای: هر مرحله یک تکه با آیکونِ تیک یا ساعت بالایش؛ تکهٔ جاری به اندازهٔ پیشرفتش پر می‌شود. */
function StageBar({ item }: { item: Fulfillment }) {
  return (
    <ol className="grid grid-cols-4 gap-2" aria-label="مراحلِ ارسال">
      {STAGES.map((title, index) => {
        const done = index < item.stage - 1 || item.progress === 100
        const current = index === item.stage - 1 && !done
        return (
          <li key={title} aria-current={current ? "step" : undefined} className="flex flex-col justify-between gap-2">
            <span className={`flex items-start gap-1.5 text-xs leading-5 sm:items-center sm:text-sm [&>svg]:mt-0.5 [&>svg]:size-4 [&>svg]:shrink-0 sm:[&>svg]:mt-0 ${done ? "font-semibold" : current ? "font-semibold" : "text-muted-foreground"}`}>
              {done ? <CheckIcon aria-label="انجام شد" className="text-success" /> : <ClockIcon aria-hidden className={current ? "text-[var(--dig-amber-600)]" : ""} />}
              <span className="min-w-0">{title}</span>
            </span>
            <span className="h-1.5 overflow-hidden rounded-full bg-muted">
              <span className="block h-full rounded-full bg-success" style={{ width: done ? "100%" : current ? `${item.progress}%` : "0%" }} />
            </span>
          </li>
        )
      })}
    </ol>
  )
}

/**
 * کارتِ ارسال (fulfillment) برای پنل یا صفحهٔ سفارش: شمارهٔ ارسال با رفتن به جزئیات، نوارِ چهارتکه‌ای
 * مراحل، فهرستِ «برچسب ← مقدار» (مبدأ، سرویس، وزن، تاریخ)، و پانویس با حمل‌کننده، وضعیت، کدِ رهگیریِ
 * قابلِ کپی و زمانِ تخمینیِ رسیدن. چند ارسالِ یک سفارش زیرِ هم.
 */
export default function OrderTrackingFulfillmentBlock() {
  return (
    <ControlSizeProvider size="xl" asChild>
      <main className="mx-auto flex w-full max-w-3xl flex-col gap-6 p-4 sm:p-6">
        <header className="flex flex-col gap-1">
          <h1 className="text-2xl leading-snug font-black">ارسال‌های سفارشِ {toFaDigits(12567)}</h1>
          <p className="text-muted-foreground">{toFaDigits(FULFILLMENTS.length)} بسته از دو انبار؛ هر کدام کدِ رهگیریِ خودش را دارد.</p>
        </header>

        {FULFILLMENTS.map((item) => {
          const delivered = item.progress === 100
          return (
            <article key={item.id} className="overflow-hidden rounded-xl border border-border bg-card shadow-card">
              <a href="#" className="flex items-center justify-between gap-3 border-b border-border px-5 py-4 hover:bg-subtle">
                <span className="font-bold">
                  ارسالِ <bdi className="tabular-nums">#{item.id}</bdi>
                </span>
                <ChevronLeftIcon aria-hidden className="size-5 text-muted-foreground" />
                <span className="sr-only">جزئیاتِ ارسال</span>
              </a>
              <div className="flex flex-col gap-5 px-5 py-5">
                <StageBar item={item} />
                <dl className="flex flex-col gap-3 text-sm">
                  {item.rows.map(([label, value]) => (
                    <div key={label} className="flex justify-between gap-4">
                      <dt className="text-muted-foreground">{label}</dt>
                      <dd className="font-medium">{value}</dd>
                    </div>
                  ))}
                </dl>
              </div>
              <footer className="flex flex-col gap-3 border-t border-border bg-subtle px-5 py-4 sm:flex-row sm:items-center sm:justify-between">
                <span className="flex items-center gap-2">
                  <span aria-hidden className={`flex size-7 items-center justify-center rounded-md text-[var(--dig-white)] [&>svg]:size-4 ${item.carrierTone}`}>
                    <TruckIcon />
                  </span>
                  <span className="font-semibold">{item.carrier}</span>
                  <Badge variant="soft" color={delivered ? "success" : "info"}>
                    {delivered ? "تحویل شد" : "در راه"}
                  </Badge>
                </span>
                <span className="flex flex-wrap items-center gap-x-3 gap-y-1 text-sm">
                  <span className="flex items-center gap-1">
                    <span className="text-muted-foreground">رهگیری:</span>
                    <bdi dir="ltr" className="font-mono font-semibold">
                      {toFaDigits(item.tracking)}
                    </bdi>
                    <ControlSizeProvider size="lg" asChild>
                      <Button
                        variant="ghost"
                        size="icon"
                        aria-label="کپیِ کدِ رهگیری"
                        onClick={() => {
                          void navigator.clipboard?.writeText(item.tracking)
                          toast.success("کدِ رهگیری کپی شد.")
                        }}
                      >
                        <CopyIcon />
                      </Button>
                    </ControlSizeProvider>
                  </span>
                  <span>
                    <span className="text-muted-foreground">{delivered ? "" : "رسیدن: "}</span>
                    <span className="font-semibold">{item.eta}</span>
                  </span>
                </span>
              </footer>
            </article>
          )
        })}
      </main>
    </ControlSizeProvider>
  )
}