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

داشبوردِ کیف‌پول

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

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

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

این بلاک از ۹ آیتم دیگ استفاده می‌کند: avatarbuttoncardchart-presetscontrol-sizeiconspersianprogresstoggle-group

پکیج‌منیجر پیش‌فرض Node.jsنصب سریع با لینک سخت و دیسک مشترکYarn نسخهٔ ۲ به بالا (Berry)رانتایم و پکیج‌منیجر Bun
npx digdesign@latest add https://docs.digdesign.ir/r/avatar.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/progress.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/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/progress.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/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/progress.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/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/progress.json https://docs.digdesign.ir/r/toggle-group.json

۲. کد صفحه

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

dashboard-wallet.tsx
"use client"

import * as React from "react"

import { ArrowUpIcon, BellIcon, MoreHorizontalIcon, PlusIcon, SearchIcon, ShopIcon, ShoppingCartIcon, SparklesIcon, TruckIcon, WalletIcon } from "@/components/icons"
import { Avatar } from "@/components/ui/avatar"
import { Button } from "@/components/ui/button"
import { Card, CardAction, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { RadialChart } from "@/components/ui/chart-presets"
import { ControlSizeProvider } from "@/components/ui/control-size"
import { Progress } from "@/components/ui/progress"
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"
import { formatNumber, toFaDigits } from "@/lib/persian"
import { stockImages } from "@/lib/stock-images"

const SPENDING = [
  { name: "خرید", value: 38 },
  { name: "رستوران", value: 24 },
  { name: "حمل‌ونقل", value: 18 },
]
const LIMITS = [
  { label: "خریدِ کارتی", used: 42_000_000, max: 80_000_000 },
  { label: "برداشتِ نقدی", used: 18_500_000, max: 20_000_000 },
  { label: "انتقالِ کارت‌به‌کارت", used: 6_000_000, max: 50_000_000 },
]
const TRANSACTIONS = [
  { icon: <ShopIcon />, title: "فروشگاهِ افق", time: "امروز، ۱۲:۴۰", amount: -1_280_000, tone: "bg-[var(--dig-orange-100)] text-[var(--dig-orange-700)] dark:bg-[var(--dig-orange-950)] dark:text-[var(--dig-orange-300)]" },
  { icon: <WalletIcon />, title: "واریزِ حقوق", time: "دیروز، ۰۹:۰۰", amount: 96_000_000, tone: "bg-[var(--dig-green-100)] text-[var(--dig-green-700)] dark:bg-[var(--dig-green-950)] dark:text-[var(--dig-green-300)]" },
  { icon: <TruckIcon />, title: "اسنپ‌باکس", time: "دیروز، ۱۸:۲۰", amount: -320_000, tone: "bg-[var(--dig-blue-100)] text-[var(--dig-blue-700)] dark:bg-[var(--dig-blue-950)] dark:text-[var(--dig-blue-300)]" },
  { icon: <ShoppingCartIcon />, title: "سوپرمارکتِ محله", time: "۱۴ مهر", amount: -2_150_000, tone: "bg-[var(--dig-violet-100)] text-[var(--dig-violet-700)] dark:bg-[var(--dig-violet-950)] dark:text-[var(--dig-violet-300)]" },
]

/**
 * نمای کلیِ کیف‌پول: سلام و نوارِ بخش‌ها، کارتِ موجودی با عددِ درشت و دو اقدام، کارتِ
 * پس‌اندازِ سبزِ روشن با نشانِ ستاره و دو هدف، حلقهٔ هزینه‌ها بر اساسِ دسته، سقف‌های کارت با
 * نوارِ مصرف (نزدیک به سقف هشدار می‌گیرد) و آخرین تراکنش‌ها.
 */
export default function DashboardWalletBlock() {
  const [section, setSection] = React.useState("overview")

  return (
    <ControlSizeProvider size="xl" asChild>
      <main className="flex w-full flex-col gap-6 bg-subtle p-4 sm:p-6">
        <header className="mx-auto flex w-full max-w-6xl flex-wrap items-center justify-between gap-4">
          <div className="flex items-center gap-3">
            <Avatar src={stockImages.personMan1.src} alt="" />
            <span className="text-lg font-bold">سلام، امیر</span>
          </div>
          <ToggleGroup type="single" variant="segmented" value={section} onValueChange={(value) => value && setSection(value as string)} aria-label="بخش‌ها" className="order-3 w-full overflow-x-auto md:order-none md:w-auto">
            <ToggleGroupItem value="overview">نمای کلی</ToggleGroupItem>
            <ToggleGroupItem value="cards">کارت‌ها</ToggleGroupItem>
            <ToggleGroupItem value="invest">سرمایه</ToggleGroupItem>
            <ToggleGroupItem value="history">تراکنش‌ها</ToggleGroupItem>
          </ToggleGroup>
          <div className="flex gap-2">
            <Button variant="ghost" size="icon" aria-label="جست‌وجو">
              <SearchIcon />
            </Button>
            <Button variant="ghost" size="icon" aria-label="اعلان‌ها">
              <BellIcon />
            </Button>
          </div>
        </header>

        <div className="mx-auto grid w-full max-w-6xl grid-cols-1 gap-4 lg:grid-cols-2">
          <Card>
            <CardHeader>
              <CardTitle className="text-sm font-medium text-muted-foreground">موجودیِ کل</CardTitle>
              <CardAction className="flex gap-1 text-sm">
                <span className="rounded-full bg-muted px-2.5 py-1 font-semibold">تومان</span>
                <span className="rounded-full px-2.5 py-1 text-muted-foreground">دلار</span>
              </CardAction>
            </CardHeader>
            <CardContent className="flex flex-col gap-8">
              <span className="flex items-center gap-3">
                <span className="text-4xl font-black tabular-nums sm:text-5xl">{formatNumber(342_180_000)}</span>
                <bdi dir="ltr" className="rounded-full bg-success-subtle px-2 py-0.5 text-xs font-semibold text-success">
                  +٪۴٫۲
                </bdi>
              </span>
              <div className="flex items-center justify-between gap-3">
                <div className="flex gap-2">
                  <Button>
                    <ArrowUpIcon className="-rotate-45" />
                    انتقال
                  </Button>
                  <Button variant="outline">
                    <PlusIcon />
                    افزایشِ موجودی
                  </Button>
                </div>
                <Button variant="ghost" size="icon" aria-label="اقدام‌های بیشتر" className="hidden sm:inline-flex">
                  <MoreHorizontalIcon />
                </Button>
              </div>
            </CardContent>
          </Card>

          <section aria-labelledby="dw-save" className="relative isolate flex flex-col justify-between gap-8 overflow-hidden rounded-xl bg-[var(--dig-green-300)] p-6 text-[var(--dig-graphite-950)]">
            <SparklesIcon aria-hidden className="absolute -end-6 -top-6 -z-10 size-48 text-[var(--dig-green-200)]" />
            <div className="flex items-start justify-between">
              <div className="flex flex-col gap-2">
                <span id="dw-save" className="w-fit rounded-full bg-[var(--dig-graphite-950)] px-3 py-1 text-xs font-semibold text-[var(--dig-green-200)]">
                  پس‌انداز
                </span>
                <span className="text-4xl font-black tabular-nums">{formatNumber(121_720_000)}</span>
                <span className="text-sm text-[var(--dig-green-900)]">سودِ روزشمار ٪{toFaDigits(23)} سالانه</span>
              </div>
            </div>
            <ul className="grid grid-cols-2 gap-3">
              {[
                ["سفرِ نوروز", 64],
                ["صندوقِ اضطرار", 88],
              ].map(([label, value]) => (
                <li key={label} className="flex flex-col gap-2 rounded-lg bg-[var(--dig-green-200)] p-3">
                  <span className="flex justify-between text-sm font-semibold">
                    {label}
                    <span className="tabular-nums">٪{toFaDigits(value)}</span>
                  </span>
                  <span aria-hidden className="h-1.5 overflow-hidden rounded-full bg-[var(--dig-green-100)]">
                    <span className="block h-full rounded-full bg-[var(--dig-graphite-950)]" style={{ width: `${value}%` }} />
                  </span>
                </li>
              ))}
            </ul>
          </section>
        </div>

        <div className="mx-auto grid w-full max-w-6xl grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
          <Card>
            <CardHeader>
              <CardTitle>هزینه‌ها</CardTitle>
              <CardAction className="text-sm text-muted-foreground">این ماه</CardAction>
            </CardHeader>
            <CardContent>
              <RadialChart
                className="h-56"
                data={SPENDING}
                nameKey="name"
                valueKey="value"
                max={100}
                showTrack
                showLabels
                centerLabel={{ value: "۱۸٫۴ م", label: "کلِ هزینه" }}
                valueFormatter={(value) => `٪${toFaDigits(value)}`}
              />
            </CardContent>
          </Card>

          <Card>
            <CardHeader>
              <CardTitle>سقفِ کارت</CardTitle>
              <CardAction className="text-sm text-muted-foreground">روزانه</CardAction>
            </CardHeader>
            <CardContent className="flex flex-col gap-5">
              {LIMITS.map((limit) => {
                const ratio = Math.round((limit.used / limit.max) * 100)
                return (
                  <div key={limit.label} className="flex flex-col gap-2">
                    <span className="flex items-baseline justify-between gap-2 text-sm">
                      <span className="font-semibold">{limit.label}</span>
                      <span className="text-muted-foreground tabular-nums">
                        {formatNumber(limit.used / 1_000_000)} از {formatNumber(limit.max / 1_000_000)} میلیون
                      </span>
                    </span>
                    <Progress value={ratio} color={ratio > 85 ? "warning" : "default"} aria-label={`مصرفِ ${limit.label}`} />
                  </div>
                )
              })}
            </CardContent>
          </Card>

          <Card className="md:col-span-2 lg:col-span-1">
            <CardHeader>
              <CardTitle>تراکنش‌ها</CardTitle>
              <CardAction>
                <Button variant="link" className="h-auto p-0">
                  همه
                </Button>
              </CardAction>
            </CardHeader>
            <CardContent>
              <ul className="flex flex-col gap-4">
                {TRANSACTIONS.map((item) => (
                  <li key={item.title} className="flex items-center gap-3">
                    <span aria-hidden className={`flex size-10 shrink-0 items-center justify-center rounded-full [&>svg]:size-5 ${item.tone}`}>
                      {item.icon}
                    </span>
                    <span className="flex flex-1 flex-col">
                      <span className="text-sm font-semibold">{item.title}</span>
                      <span className="text-xs text-muted-foreground">{item.time}</span>
                    </span>
                    <bdi dir="ltr" className={`text-sm font-bold tabular-nums ${item.amount > 0 ? "text-success" : ""}`}>
                      {item.amount > 0 ? "+" : "−"}
                      {formatNumber(Math.abs(item.amount))}
                    </bdi>
                  </li>
                ))}
              </ul>
            </CardContent>
          </Card>
        </div>
      </main>
    </ControlSizeProvider>
  )
}