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

پروندهٔ مشتری (CRM): سرِ صفحه با عکس، برچسب‌ها و تماس و فاکتورِ تازه؛ سه آمارِ کلیدی؛ تبِ فاکتورها و یادداشت‌ها با ثبتِ یادداشت؛ کارت‌های اطلاعاتِ تماس و مسئولِ حساب.

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

این بلاک از ۱۳ آیتم دیگ استفاده می‌کند: avatarbadgebreadcrumbbuttoncardcontrol-sizeiconspersianstat-tiletabletextareatoasttoggle-group

پکیج‌منیجر پیش‌فرض 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/breadcrumb.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/stat-tile.json https://docs.digdesign.ir/r/table.json https://docs.digdesign.ir/r/textarea.json https://docs.digdesign.ir/r/toast.json https://docs.digdesign.ir/r/toggle-group.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/breadcrumb.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/stat-tile.json https://docs.digdesign.ir/r/table.json https://docs.digdesign.ir/r/textarea.json https://docs.digdesign.ir/r/toast.json https://docs.digdesign.ir/r/toggle-group.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/breadcrumb.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/stat-tile.json https://docs.digdesign.ir/r/table.json https://docs.digdesign.ir/r/textarea.json https://docs.digdesign.ir/r/toast.json https://docs.digdesign.ir/r/toggle-group.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/breadcrumb.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/stat-tile.json https://docs.digdesign.ir/r/table.json https://docs.digdesign.ir/r/textarea.json https://docs.digdesign.ir/r/toast.json https://docs.digdesign.ir/r/toggle-group.json

۲. کد صفحه

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

customer-detail.tsx
"use client"

import * as React from "react"

import { EnvelopeIcon, MoreHorizontalIcon, PhoneIcon, PlusIcon, ReceiptIcon } from "@/components/icons"
import { Avatar } from "@/components/ui/avatar"
import { Badge } from "@/components/ui/badge"
import {
  Breadcrumb,
  BreadcrumbItem,
  BreadcrumbLink,
  BreadcrumbList,
  BreadcrumbPage,
  BreadcrumbSeparator,
} from "@/components/ui/breadcrumb"
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { ControlSizeProvider } from "@/components/ui/control-size"
import { StatTile, StatTileContent, StatTileHeader, StatTileLabel, StatTileValue } from "@/components/ui/stat-tile"
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
import { Textarea } from "@/components/ui/textarea"
import { toast } from "@/components/ui/toast"
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"
import { formatCurrency, formatJalali, toFaDigits } from "@/lib/persian"
import { stockImages } from "@/lib/stock-images"

const INVOICES = [
  { id: "۱۴۰۵-۰۷۲۱", date: "2026-10-02", amount: 12_400_000, status: "unpaid" as const },
  { id: "۱۴۰۵-۰۶۴۰", date: "2026-09-05", amount: 8_900_000, status: "paid" as const },
  { id: "۱۴۰۵-۰۵۱۲", date: "2026-08-03", amount: 15_200_000, status: "paid" as const },
]

const NOTES = [
  { who: "نیما رضایی", photo: stockImages.personMan1, text: "برای قراردادِ سالانه تخفیفِ ۱۰٪ خواستند؛ تا آخرِ هفته پیشنهاد می‌فرستم.", time: "۲ روز پیش" },
  { who: "مهسا کریمی", photo: stockImages.personWoman1, text: "تماسِ آشنایی انجام شد؛ ۲۴ شعبه دارند و دنبالِ گزارشِ یکپارچه‌اند.", time: "۱۲ مهر" },
]

/**
 * جزئیاتِ مشتری (CRM): سرِ صفحه با نام، برچسب‌ها و کنش‌های تماس؛ سه آمارِ کلیدی،
 * تب‌های فاکتورها و یادداشت‌ها، و ستونِ کنارِ اطلاعاتِ تماس و مسئولِ حساب.
 */
export default function CustomerDetailBlock() {
  const [tab, setTab] = React.useState("invoices")
  const [note, setNote] = React.useState("")

  return (
    <ControlSizeProvider size="xl" asChild>
      <main className="mx-auto flex w-full max-w-6xl flex-col gap-8 p-4 sm:p-6">
        <Breadcrumb>
          <BreadcrumbList>
            <BreadcrumbItem>
              <BreadcrumbLink href="#">مشتریان</BreadcrumbLink>
            </BreadcrumbItem>
            <BreadcrumbSeparator />
            <BreadcrumbItem>
              <BreadcrumbPage>کافه رست</BreadcrumbPage>
            </BreadcrumbItem>
          </BreadcrumbList>
        </Breadcrumb>

        <header className="flex flex-wrap items-center justify-between gap-x-4 gap-y-8">
          <div className="flex items-center gap-4">
            {/* eslint-disable-next-line @next/next/no-img-element -- عکس از کتابخانهٔ تصویرِ دیگ */}
            <img src={stockImages.projectCafeBrand.src} alt="" className="size-16 rounded-xl object-cover" />
            <div className="flex flex-col gap-2">
              <h1 className="text-2xl font-bold">کافه رست</h1>
              <div className="flex flex-wrap gap-2">
                <Badge variant="soft" color="success">
                  مشتری فعال
                </Badge>
                <Badge variant="soft">زنجیره‌ای · ۲۴ شعبه</Badge>
              </div>
            </div>
          </div>
          <div className="flex gap-2">
            <Button variant="outline">
              <PhoneIcon />
              تماس
            </Button>
            <Button>
              <ReceiptIcon />
              فاکتورِ تازه
            </Button>
            <Button variant="ghost" size="icon" aria-label="کنش‌های بیشتر">
              <MoreHorizontalIcon />
            </Button>
          </div>
        </header>

        <div className="grid grid-cols-1 gap-4 sm:grid-cols-3">
          {[
            { label: "مجموعِ خرید", value: formatCurrency(36_500_000) },
            { label: "بدهی باز", value: formatCurrency(12_400_000) },
            { label: "مشتری از", value: formatJalali(new Date("2025-02-10"), "long") },
          ].map((stat) => (
            <StatTile key={stat.label}>
              <StatTileHeader>
                <StatTileLabel>{stat.label}</StatTileLabel>
              </StatTileHeader>
              <StatTileContent>
                <StatTileValue className="text-2xl">{stat.value}</StatTileValue>
              </StatTileContent>
            </StatTile>
          ))}
        </div>

        <div className="grid grid-cols-1 items-start gap-6 lg:grid-cols-[minmax(0,1fr)_20rem]">
          <section className="flex flex-col gap-6">
            <ToggleGroup type="single" variant="segmented" value={tab} onValueChange={(value) => value && setTab(value as string)} aria-label="بخش‌ها" className="self-start">
              <ToggleGroupItem value="invoices">فاکتورها</ToggleGroupItem>
              <ToggleGroupItem value="notes">یادداشت‌ها</ToggleGroupItem>
            </ToggleGroup>

            {tab === "invoices" ? (
              <div>
                <Table stackOnMobile variant="card">
                  <TableHeader>
                    <TableRow>
                      <TableHead>شمارهٔ فاکتور</TableHead>
                      <TableHead>تاریخ</TableHead>
                      <TableHead>مبلغ</TableHead>
                      <TableHead>وضعیت</TableHead>
                    </TableRow>
                  </TableHeader>
                  <TableBody>
                    {INVOICES.map((invoice) => (
                      <TableRow key={invoice.id}>
                        <TableCell className="font-medium tabular-nums">{invoice.id}</TableCell>
                        <TableCell>{formatJalali(new Date(invoice.date), "long")}</TableCell>
                        <TableCell className="tabular-nums">{formatCurrency(invoice.amount)}</TableCell>
                        <TableCell>
                          <Badge variant="soft" color={invoice.status === "paid" ? "success" : "warning"}>
                            {invoice.status === "paid" ? "پرداخت‌شده" : "در انتظارِ پرداخت"}
                          </Badge>
                        </TableCell>
                      </TableRow>
                    ))}
                  </TableBody>
                </Table>
              </div>
            ) : (
              <div className="flex flex-col gap-4">
                <div className="flex flex-col gap-3 rounded-xl border border-border p-4">
                  <Textarea aria-label="یادداشتِ تازه" rows={3} value={note} onChange={(event) => setNote(event.target.value)} placeholder="یادداشتی دربارهٔ این مشتری…" />
                  <Button
                    className="self-end"
                    disabled={!note.trim()}
                    onClick={() => {
                      setNote("")
                      toast.success("یادداشت ثبت شد.")
                    }}
                  >
                    <PlusIcon />
                    ثبتِ یادداشت
                  </Button>
                </div>
                <ul className="flex flex-col gap-3">
                  {NOTES.map((item) => (
                    <li key={item.text} className="flex gap-3 rounded-xl bg-subtle p-4">
                      <Avatar alt={item.who} src={item.photo.src} />
                      <div className="flex min-w-0 flex-1 flex-col gap-1">
                        <span className="flex items-center justify-between gap-2 text-sm">
                          <span className="font-semibold">{item.who}</span>
                          <span className="text-xs text-muted-foreground">{item.time}</span>
                        </span>
                        <p className="text-sm leading-7">{item.text}</p>
                      </div>
                    </li>
                  ))}
                </ul>
              </div>
            )}
          </section>

          <div className="flex flex-col gap-6">
            <Card>
              <CardHeader>
                <CardTitle className="text-base">اطلاعاتِ تماس</CardTitle>
              </CardHeader>
              <CardContent>
                <dl className="flex flex-col gap-4 text-sm">
                  <div className="flex items-center gap-3">
                    <Avatar alt="رضا امینی" src={stockImages.personMan2.src} />
                    <div className="flex flex-col">
                      <dt className="text-muted-foreground">رابط</dt>
                      <dd className="font-medium">رضا امینی، مدیرِ مالی</dd>
                    </div>
                  </div>
                  <div className="flex items-center gap-3">
                    <PhoneIcon aria-hidden className="size-4 text-muted-foreground" />
                    <dd>
                      <bdi dir="ltr">{toFaDigits("021-88123456")}</bdi>
                    </dd>
                  </div>
                  <div className="flex items-center gap-3">
                    <EnvelopeIcon aria-hidden className="size-4 text-muted-foreground" />
                    <dd>
                      <bdi dir="ltr">finance@rest.example</bdi>
                    </dd>
                  </div>
                </dl>
              </CardContent>
            </Card>
            <Card>
              <CardHeader>
                <CardTitle className="text-base">مسئولِ حساب</CardTitle>
              </CardHeader>
              <CardContent className="flex items-center gap-3">
                <Avatar alt="نیما رضایی" src={stockImages.personMan1.src} />
                <div className="flex flex-1 flex-col text-sm">
                  <span className="font-medium">نیما رضایی</span>
                  <span className="text-muted-foreground">تماسِ بعدی: ۱۸ مهر</span>
                </div>
                <Button variant="outline">تغییر</Button>
              </CardContent>
            </Card>
          </div>
        </div>
      </main>
    </ControlSizeProvider>
  )
}