بلاک‌ها · پنل مدیریت

سفارش‌ها روی بورد

پیش‌نمایش تمام‌صفحه

سه ستونِ مرحله با شمار، کارتِ سفارش با مشتری، مبلغ و سن، برچسبِ «دیر شده» و دکمهٔ قدمِ بعد به‌جای کشیدن.

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

این بلاک از ۵ آیتم دیگ استفاده می‌کند: 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.json
pnpm 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.json
yarn 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.json
bunx --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>
  )
}