جزئیات مشتری
پیشنمایش تمامصفحهپروندهٔ مشتری (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.jsonpnpm 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.jsonyarn 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.jsonbunx --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>
)
}