داشبوردِ بازاریابی
پیشنمایش تمامصفحهنوارِ شش شاخص با تغییرِ رنگی، نمودارِ روندِ سهسری، جدولِ سرنخها با کیفیتِ برچسبدار و نشانِ تازه، و ستونِ شبکهٔ اجتماعی با دنبالکننده، حلقهٔ تعامل و بهترین پستها.
۱. نصب کامپوننتها
این بلاک از ۸ آیتم دیگ استفاده میکند: badgecardchart-presetscontrol-sizepersianprogresstabletoggle-group
پکیجمنیجر پیشفرض Node.jsنصب سریع با لینک سخت و دیسک مشترکYarn نسخهٔ ۲ به بالا (Berry)رانتایم و پکیجمنیجر Bun
npx digdesign@latest add https://docs.digdesign.ir/r/badge.json https://docs.digdesign.ir/r/card.json https://docs.digdesign.ir/r/chart-presets.json https://docs.digdesign.ir/r/control-size.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/progress.json https://docs.digdesign.ir/r/table.json https://docs.digdesign.ir/r/toggle-group.jsonpnpm dlx digdesign@latest add https://docs.digdesign.ir/r/badge.json https://docs.digdesign.ir/r/card.json https://docs.digdesign.ir/r/chart-presets.json https://docs.digdesign.ir/r/control-size.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/progress.json https://docs.digdesign.ir/r/table.json https://docs.digdesign.ir/r/toggle-group.jsonyarn dlx digdesign@latest add https://docs.digdesign.ir/r/badge.json https://docs.digdesign.ir/r/card.json https://docs.digdesign.ir/r/chart-presets.json https://docs.digdesign.ir/r/control-size.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/progress.json https://docs.digdesign.ir/r/table.json https://docs.digdesign.ir/r/toggle-group.jsonbunx --bun digdesign@latest add https://docs.digdesign.ir/r/badge.json https://docs.digdesign.ir/r/card.json https://docs.digdesign.ir/r/chart-presets.json https://docs.digdesign.ir/r/control-size.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/progress.json https://docs.digdesign.ir/r/table.json https://docs.digdesign.ir/r/toggle-group.json۲. کد صفحه
کد را در app/<route>/page.tsx بگذارید و متنها، داده و هندلرها را با محصول خودتان عوض کنید.
dashboard-marketing.tsx
"use client"
import * as React from "react"
import { Badge } from "@/components/ui/badge"
import { Card, CardAction, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { LineChart } from "@/components/ui/chart-presets"
import { ControlSizeProvider } from "@/components/ui/control-size"
import { ProgressCircle } from "@/components/ui/progress"
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"
import { formatNumber, toFaDigits } from "@/lib/persian"
import { stockImages, type StockImageName } from "@/lib/stock-images"
const KPIS = [
{ label: "بازدید", value: "۹۸۲", delta: "+٪۵", good: true },
{ label: "بازگشتِ سرمایه", value: "+٪۱۵", delta: "+٪۳", good: true },
{ label: "تبدیل", value: "۴۷۳", delta: "+٪۹", good: true },
{ label: "هزینه", value: "۸۸ م", delta: "−٪۲", good: true },
{ label: "نرخِ کلیک", value: "٪۲٫۵", delta: "−٪۰٫۲", good: false },
{ label: "هزینهٔ هر جذب", value: "۵۴ ه", delta: "−٪۳", good: true },
]
const TREND = [
{ label: "تیر", traffic: 42, roi: 30, conversions: 22 },
{ label: "", traffic: 55, roi: 34, conversions: 25 },
{ label: "", traffic: 48, roi: 38, conversions: 28 },
{ label: "مرداد", traffic: 63, roi: 36, conversions: 31 },
{ label: "", traffic: 58, roi: 44, conversions: 30 },
{ label: "", traffic: 71, roi: 47, conversions: 36 },
{ label: "شهریور", traffic: 66, roi: 52, conversions: 39 },
{ label: "", traffic: 78, roi: 55, conversions: 42 },
]
type Quality = "review" | "good" | "neutral" | "irrelevant"
const QUALITY: Record<Quality, { label: string; color: "warning" | "success" | "default" | "destructive" }> = {
review: { label: "نیاز به بررسی", color: "warning" },
good: { label: "خوب", color: "success" },
neutral: { label: "خنثی", color: "default" },
irrelevant: { label: "نامرتبط", color: "destructive" },
}
const LEADS: { id: string; date: string; company: string; email: string; quality: Quality; city: string; fresh?: boolean }[] = [
{ id: "1", date: "امروز، ۱۴:۱۲", company: "—", email: "contact@helpdesk.ir", quality: "review", city: "تهران", fresh: true },
{ id: "2", date: "امروز، ۰۸:۳۴", company: "نوآوران", email: "s.chen@noavaran.ir", quality: "review", city: "شیراز", fresh: true },
{ id: "3", date: "۱۴ مهر، ۱۷:۵۷", company: "وبمیل", email: "maria@webmail.ir", quality: "good", city: "اصفهان" },
{ id: "4", date: "۱۴ مهر، ۱۳:۴۰", company: "—", email: "john@startup.ir", quality: "good", city: "مشهد" },
{ id: "5", date: "۱۳ مهر، ۱۰:۲۲", company: "دیزاینهاب", email: "hello@designhub.ir", quality: "neutral", city: "تبریز" },
{ id: "6", date: "۱۲ مهر، ۰۹:۰۱", company: "پروتون", email: "k@proton.ir", quality: "irrelevant", city: "کرج" },
]
const POSTS: { image: StockImageName; date: string }[] = [
{ image: "projectMobileApp", date: "۳۰ شهریور" },
{ image: "projectDashboard", date: "۱ مهر" },
{ image: "coverWorkspace", date: "۳ مهر" },
{ image: "projectCafeBrand", date: "۵ مهر" },
]
/**
* داشبوردِ بازاریابی: نوارِ شش شاخص با تغییرِ رنگی (با علامتِ + و − نه فقط رنگ)، نمودارِ روندِ
* سهسری با بازهٔ سگمنت، جدولِ سرنخها با کیفیتِ برچسبدار و نشانِ تازه، و ستونِ شبکههای اجتماعی
* با دنبالکننده، حلقهٔ تعامل، ریزِ آمار و بهترین پستها. جدول در موبایل اسکرولِ افقیِ خودش را دارد.
*/
export default function DashboardMarketingBlock() {
const [range, setRange] = React.useState("3m")
const [tab, setTab] = React.useState("leads")
const data = range === "1m" ? TREND.slice(-3) : TREND
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 xl:grid-cols-[minmax(0,1fr)_19rem]">
<div className="flex min-w-0 flex-col gap-6">
<header className="flex flex-col gap-1">
<h1 className="text-2xl leading-snug font-black">نمای زنده</h1>
<p className="text-muted-foreground">کمپینِ پاییز · بهروز تا ۵ دقیقه پیش</p>
</header>
<ul className="grid grid-cols-2 divide-border rounded-xl border border-border bg-card shadow-card sm:grid-cols-3 lg:grid-cols-6 [&>li]:border-border max-sm:[&>li:nth-child(odd)]:border-e max-sm:[&>li:nth-child(-n+4)]:border-b sm:max-lg:[&>li:nth-child(-n+3)]:border-b sm:max-lg:[&>li:not(:nth-child(3n))]:border-e lg:[&>li:not(:last-child)]:border-e">
{KPIS.map((kpi) => (
<li key={kpi.label} className="flex flex-col gap-1 p-4">
<span className="text-xs text-muted-foreground">{kpi.label}</span>
<span className="flex items-baseline gap-1.5">
<span className="text-lg font-black tabular-nums">{kpi.value}</span>
<bdi dir="ltr" className={`text-xs font-semibold ${kpi.good ? "text-success" : "text-destructive"}`}>
{kpi.delta}
</bdi>
</span>
</li>
))}
</ul>
<Card>
<CardHeader>
<CardTitle className="text-lg">روندها</CardTitle>
<CardAction>
<ToggleGroup type="single" variant="segmented" value={range} onValueChange={(value) => value && setRange(value as string)} aria-label="بازه">
<ToggleGroupItem value="1m">۱ ماه</ToggleGroupItem>
<ToggleGroupItem value="3m">۳ ماه</ToggleGroupItem>
</ToggleGroup>
</CardAction>
</CardHeader>
<CardContent>
<LineChart
className="h-60"
data={data}
categoryKey="label"
smooth
showPoints={false}
series={[
{ key: "traffic", label: "بازدید", color: "var(--dig-orange-500)" },
{ key: "roi", label: "بازگشتِ سرمایه", color: "var(--dig-blue-500)" },
{ key: "conversions", label: "تبدیل", color: "var(--dig-violet-400)" },
]}
valueFormatter={(value) => formatNumber(value)}
/>
</CardContent>
</Card>
<Card className="gap-4">
<CardHeader>
<ToggleGroup type="single" variant="segmented" value={tab} onValueChange={(value) => value && setTab(value as string)} aria-label="گزارش" className="max-w-full overflow-x-auto">
<ToggleGroupItem value="leads">کیفیتِ سرنخ</ToggleGroupItem>
<ToggleGroupItem value="daily">روزبهروز</ToggleGroupItem>
<ToggleGroupItem value="keywords">کلیدواژهها</ToggleGroupItem>
</ToggleGroup>
</CardHeader>
<CardContent>
<Table stackOnMobile className="min-w-[38rem]">
<TableHeader>
<TableRow className="hover:bg-card">
<TableHead>تاریخ</TableHead>
<TableHead>شرکت</TableHead>
<TableHead>ایمیل</TableHead>
<TableHead>کیفیت</TableHead>
<TableHead>شهر</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{LEADS.map((lead) => (
<TableRow key={lead.id}>
<TableCell className="font-medium whitespace-nowrap">
<span className="flex items-center gap-2">
{lead.fresh && <span aria-label="تازه" className="size-2 rounded-full bg-[var(--dig-orange-500)]" />}
{lead.date}
</span>
</TableCell>
<TableCell className={lead.company === "—" ? "text-muted-foreground" : "font-semibold"}>{lead.company === "—" ? "نامشخص" : lead.company}</TableCell>
<TableCell>
<bdi dir="ltr" className="text-muted-foreground">
{lead.email}
</bdi>
</TableCell>
<TableCell>
<Badge variant="soft" color={QUALITY[lead.quality].color}>
{QUALITY[lead.quality].label}
</Badge>
</TableCell>
<TableCell>{lead.city}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</CardContent>
</Card>
</div>
<aside className="flex flex-col gap-6">
<Card className="gap-4">
<CardHeader>
<CardTitle className="text-lg">اینستاگرام</CardTitle>
<CardAction>
<span className="text-xs text-muted-foreground">۷ روز</span>
</CardAction>
</CardHeader>
<CardContent className="flex flex-col gap-4">
<div className="grid grid-cols-2 gap-3 xl:grid-cols-1">
<div className="flex flex-col gap-1 rounded-lg bg-subtle p-3">
<span className="text-xs text-muted-foreground">دنبالکننده</span>
<span className="flex items-baseline gap-1.5">
<span className="text-lg font-black tabular-nums">{formatNumber(135_145)}</span>
<bdi dir="ltr" className="text-xs font-semibold text-success">
+{toFaDigits(16)}
</bdi>
</span>
</div>
<div className="flex items-center justify-between gap-2 rounded-lg bg-subtle p-3">
<span className="flex flex-col gap-1">
<span className="text-xs text-muted-foreground">تعامل</span>
<span className="text-lg font-black tabular-nums">{formatNumber(1_356)}</span>
</span>
<ProgressCircle value={68} size="md" aria-label="نرخِ تعامل ۶۸ درصد" className="[--progress:var(--dig-orange-500)]" />
</div>
</div>
<dl className="grid grid-cols-2 gap-x-4 gap-y-2 text-sm">
{[
["پسند", 697],
["نظر", 22],
["ذخیره", 566],
["اشتراک", 71],
].map(([label, value]) => (
<div key={label} className="flex justify-between gap-2">
<dt className="text-muted-foreground">{label}</dt>
<dd className="font-semibold tabular-nums">{formatNumber(value as number)}</dd>
</div>
))}
</dl>
<div className="flex flex-col gap-2">
<span className="text-sm font-bold">بهترین پستها</span>
<ul className="grid grid-cols-4 gap-2">
{POSTS.map((post) => (
<li key={post.date} className="flex flex-col items-center gap-1">
{/* eslint-disable-next-line @next/next/no-img-element -- عکس از کتابخانهٔ تصویرِ دیگ */}
<img src={stockImages[post.image].src} alt="" loading="lazy" className="aspect-square w-full rounded-lg object-cover" />
<span className="text-[0.6875rem] text-muted-foreground">{post.date}</span>
</li>
))}
</ul>
</div>
</CardContent>
</Card>
</aside>
</main>
</ControlSizeProvider>
)
}