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

داشبوردِ مالی: بنرِ گرادیانیِ موجودیِ نقد با دو اقدام، نوارِ سه شاخص با روند، نمودارِ ناحیه‌ایِ این ماه/ماهِ قبل، پرداخت‌های پیشِ رو با هشدار و فعالیت‌های اخیرِ تیم.

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

این بلاک از ۹ آیتم دیگ استفاده می‌کند: avatarbadgebuttoncardchart-presetscontrol-sizeiconspersiantoggle-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/button.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/icons.json https://docs.digdesign.ir/r/persian.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/button.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/icons.json https://docs.digdesign.ir/r/persian.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/button.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/icons.json https://docs.digdesign.ir/r/persian.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/button.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/icons.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/toggle-group.json

۲. کد صفحه

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

dashboard-finance.tsx
"use client"

import * as React from "react"

import { ArrowUpIcon, CalendarIcon, CreditCardIcon, ReceiptIcon, TrendingUpIcon, WalletIcon } from "@/components/icons"
import { Avatar } from "@/components/ui/avatar"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import { Card, CardAction, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { AreaChart } from "@/components/ui/chart-presets"
import { ControlSizeProvider } from "@/components/ui/control-size"
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"
import { formatCurrency, formatNumber, toFaDigits } from "@/lib/persian"
import { avatarPhoto } from "@/lib/stock-images"

const WEEKS = [
  { label: "۱ مهر", now: 62, last: 54 },
  { label: "۳ مهر", now: 70, last: 58 },
  { label: "۵ مهر", now: 66, last: 63 },
  { label: "۷ مهر", now: 84, last: 61 },
  { label: "۹ مهر", now: 78, last: 70 },
  { label: "۱۱ مهر", now: 92, last: 68 },
  { label: "۱۳ مهر", now: 88, last: 74 },
  { label: "۱۵ مهر", now: 104, last: 77 },
]
const KPIS = [
  { icon: <ArrowUpIcon />, label: "درآمدِ این ماه", value: "۱٫۱۸ میلیارد", trend: 19, good: true },
  { icon: <ReceiptIcon />, label: "هزینهٔ این ماه", value: "۷۶۰ میلیون", trend: -5, good: true },
  { icon: <TrendingUpIcon />, label: "حاشیهٔ سود", value: "٪۳۶", trend: 4, good: true },
]
const BILLS = [
  { title: "اجارهٔ دفتر", due: "۳ روز دیگر", amount: 85_000_000, urgent: true },
  { title: "لیستِ بیمهٔ مهر", due: "۱۲ روز دیگر", amount: 142_000_000 },
  { title: "قسطِ وامِ تجهیزات", due: "۱۸ روز دیگر", amount: 36_500_000 },
]
const ACTIVITY = [
  { name: "سارا محمدی", gender: "female" as const, action: "فاکتورِ ۱۰۴۲ را پرداخت کرد", amount: "+۴۸", time: "۱۰:۱۵" },
  { name: "امیر رضایی", gender: "male" as const, action: "هزینهٔ تبلیغات را ثبت کرد", amount: "−۱۲", time: "۰۹:۴۰" },
  { name: "نگار احمدی", gender: "female" as const, action: "صورت‌حسابِ شهریور را بست", amount: null, time: "دیروز" },
]

/**
 * داشبوردِ مالی: سلام و تاریخ، بنرِ طراحی‌شدهٔ «موجودیِ نقد» (گرادیانِ برند با پترن، عددِ درشت،
 * تغییر به کلمه و دو اقدامِ اصلی)، سه شاخص با جداکننده و روندِ رنگی، نمودارِ ناحیه‌ایِ این ماه
 * در برابرِ ماهِ قبل، پرداخت‌های پیشِ رو با هشدارِ نزدیک‌ترین، و فعالیت‌های اخیرِ تیم با عکس.
 */
export default function DashboardFinanceBlock() {
  const [range, setRange] = React.useState("month")
  const data = range === "week" ? WEEKS.slice(-4) : WEEKS

  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-wrap items-end justify-between gap-x-4 gap-y-6">
          <div className="flex flex-col gap-1">
            <h1 className="text-2xl leading-snug font-black sm:text-3xl">سلام آرمیتا</h1>
            <p className="text-muted-foreground">وضعِ مالیِ کسب‌وکار؛ تا هدفِ ماه کمی مانده.</p>
          </div>
          <span className="flex items-center gap-2 rounded-full border border-border px-4 py-2 text-sm [&>svg]:size-4 [&>svg]:text-muted-foreground">
            <CalendarIcon aria-hidden />
            ۱۵ مهر ۱۴۰۵
          </span>
        </header>

        <div className="grid grid-cols-1 items-start gap-6 lg:grid-cols-[minmax(0,1fr)_22rem]">
          <div className="flex min-w-0 flex-col gap-6">
            {/* بنرِ موجودی: گرادیان، پترنِ نقطه‌ای و حلقهٔ تزئینی؛ عدد و اقدام‌ها روی آن. */}
            <section aria-labelledby="cash-title" className="relative isolate flex flex-col gap-6 overflow-hidden rounded-xl bg-[linear-gradient(135deg,var(--dig-violet-700),var(--dig-indigo-800))] p-6 text-[var(--dig-white)] sm:p-8">
              <span aria-hidden className="pointer-events-none absolute inset-0 -z-10 bg-[radial-gradient(circle_at_90%_0%,var(--dig-violet-400),transparent_45%),radial-gradient(circle_at_0%_100%,var(--dig-blue-500),transparent_40%)]" />
              <span aria-hidden className="pointer-events-none absolute inset-0 -z-10 bg-[radial-gradient(color-mix(in_oklab,var(--dig-white)_30%,transparent)_1.2px,transparent_1.3px)] [background-size:18px_18px] [mask-image:linear-gradient(to_left,black,transparent_60%)]" />
              <span aria-hidden className="pointer-events-none absolute -end-14 -bottom-20 -z-10 size-56 rounded-full border-[24px] border-[color-mix(in_oklab,var(--dig-white)_10%,transparent)]" />
              <div className="flex items-start justify-between gap-4">
                <div className="flex flex-col gap-2">
                  <span id="cash-title" className="flex items-center gap-2 text-sm text-[var(--dig-violet-100)] [&>svg]:size-4">
                    <WalletIcon aria-hidden />
                    موجودیِ نقد
                  </span>
                  <span className="text-3xl font-black tabular-nums sm:text-4xl">{formatCurrency(1_284_000_000)}</span>
                  <span className="w-fit rounded-full bg-[color-mix(in_oklab,var(--dig-white)_16%,var(--dig-violet-800))] px-3 py-1 text-xs font-semibold">٪۱۲ بیشتر از ماهِ قبل</span>
                </div>
              </div>
              <div className="flex flex-wrap gap-3">
                <Button className="bg-[var(--dig-white)] text-[var(--dig-violet-800)] hover:bg-[var(--dig-violet-50)]">
                  <ReceiptIcon />
                  فاکتورِ تازه
                </Button>
                <Button className="bg-[color-mix(in_oklab,var(--dig-white)_16%,var(--dig-violet-800))] text-[var(--dig-white)] hover:bg-[color-mix(in_oklab,var(--dig-white)_24%,var(--dig-violet-800))]">
                  <CreditCardIcon />
                  پرداخت
                </Button>
              </div>
            </section>

            {/* سه شاخص در یک نوار با جداکننده؛ روندِ مثبت/منفی با کلمه و رنگ. */}
            <ul className="grid grid-cols-1 divide-y divide-border rounded-xl border border-border bg-card sm:grid-cols-3 sm:divide-x sm:divide-y-0 rtl:sm:divide-x-reverse">
              {KPIS.map((kpi) => (
                <li key={kpi.label} className="flex items-center gap-3 p-5">
                  <span aria-hidden className="flex size-11 shrink-0 items-center justify-center rounded-full bg-muted [&>svg]:size-5">
                    {kpi.icon}
                  </span>
                  <span className="flex flex-col gap-0.5">
                    <span className="text-sm text-muted-foreground">{kpi.label}</span>
                    <span className="flex items-baseline gap-2">
                      <span className="text-xl font-black tabular-nums">{kpi.value}</span>
                      <bdi dir="ltr" className={`text-xs font-semibold ${kpi.good ? "text-success" : "text-destructive"}`}>
                        {kpi.trend > 0 ? "+" : "−"}٪{toFaDigits(Math.abs(kpi.trend))}
                      </bdi>
                    </span>
                  </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="week">هفته</ToggleGroupItem>
                    <ToggleGroupItem value="month">ماه</ToggleGroupItem>
                  </ToggleGroup>
                </CardAction>
              </CardHeader>
              <CardContent>
                <AreaChart
                  className="h-64"
                  data={data}
                  categoryKey="label"
                  smooth
                  showPoints={false}
                  series={[
                    { key: "now", label: "این ماه", color: "var(--dig-violet-500)" },
                    { key: "last", label: "ماهِ قبل", color: "var(--dig-amber-500)" },
                  ]}
                  valueFormatter={(value) => `${formatNumber(value)} میلیون`}
                />
              </CardContent>
            </Card>
          </div>

          <div className="flex flex-col gap-6">
            <Card>
              <CardHeader>
                <CardTitle className="text-lg">پرداخت‌های پیشِ رو</CardTitle>
              </CardHeader>
              <CardContent>
                <ul className="flex flex-col divide-y divide-border">
                  {BILLS.map((bill) => (
                    <li key={bill.title} className="flex items-center justify-between gap-3 py-4 first:pt-0 last:pb-0">
                      <span className="flex flex-col gap-1">
                        <span className="font-medium">{bill.title}</span>
                        {bill.urgent ? (
                          <Badge variant="soft" color="warning" className="self-start">
                            {bill.due}
                          </Badge>
                        ) : (
                          <span className="text-sm text-muted-foreground">{bill.due}</span>
                        )}
                      </span>
                      <span className="font-bold tabular-nums">{formatCurrency(bill.amount)}</span>
                    </li>
                  ))}
                </ul>
              </CardContent>
            </Card>

            <Card>
              <CardHeader>
                <CardTitle className="text-lg">فعالیت‌های اخیر</CardTitle>
              </CardHeader>
              <CardContent>
                <ul className="flex flex-col gap-5">
                  {ACTIVITY.map((item) => (
                    <li key={item.name + item.time} className="flex items-start gap-3">
                      <Avatar alt={item.name} src={avatarPhoto(item.name, item.gender)} className="size-10" />
                      <span className="flex min-w-0 flex-1 flex-col gap-0.5">
                        <span className="flex items-center justify-between gap-2">
                          <span className="text-sm font-bold">{item.name}</span>
                          <span className="text-xs text-muted-foreground tabular-nums">{item.time}</span>
                        </span>
                        <span className="text-sm text-muted-foreground">{item.action}</span>
                        {item.amount && (
                          <span className={`text-sm font-bold tabular-nums ${item.amount.startsWith("+") ? "text-success" : "text-foreground"}`}>
                            <bdi dir="ltr">{item.amount}</bdi> میلیون تومان
                          </span>
                        )}
                      </span>
                    </li>
                  ))}
                </ul>
              </CardContent>
            </Card>
          </div>
        </div>
      </main>
    </ControlSizeProvider>
  )
}