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

سفارش‌ها، نمای دوتکه

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

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

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

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