تراکنشها
پیشنمایش تمامصفحهفهرست تراکنش با فیلتر بازهٔ تاریخ و نوع، مبلغ علامتدار و خروجی.
۱. نصب کامپوننتها
این بلاک از ۱۱ آیتم دیگ استفاده میکند: badgebuttoncontrol-sizedate-range-pickerempty-stateiconspersiansearch-fieldstat-tiletabletoggle-group
پکیجمنیجر پیشفرض Node.jsنصب سریع با لینک سخت و دیسک مشترکYarn نسخهٔ ۲ به بالا (Berry)رانتایم و پکیجمنیجر Bun
npx digdesign@latest add https://docs.digdesign.ir/r/badge.json https://docs.digdesign.ir/r/button.json https://docs.digdesign.ir/r/control-size.json https://docs.digdesign.ir/r/date-range-picker.json https://docs.digdesign.ir/r/empty-state.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/search-field.json https://docs.digdesign.ir/r/stat-tile.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/button.json https://docs.digdesign.ir/r/control-size.json https://docs.digdesign.ir/r/date-range-picker.json https://docs.digdesign.ir/r/empty-state.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/search-field.json https://docs.digdesign.ir/r/stat-tile.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/button.json https://docs.digdesign.ir/r/control-size.json https://docs.digdesign.ir/r/date-range-picker.json https://docs.digdesign.ir/r/empty-state.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/search-field.json https://docs.digdesign.ir/r/stat-tile.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/button.json https://docs.digdesign.ir/r/control-size.json https://docs.digdesign.ir/r/date-range-picker.json https://docs.digdesign.ir/r/empty-state.json https://docs.digdesign.ir/r/icons.json https://docs.digdesign.ir/r/persian.json https://docs.digdesign.ir/r/search-field.json https://docs.digdesign.ir/r/stat-tile.json https://docs.digdesign.ir/r/table.json https://docs.digdesign.ir/r/toggle-group.json۲. کد صفحه
کد را در app/<route>/page.tsx بگذارید و متنها، داده و هندلرها را با محصول خودتان عوض کنید.
transactions.tsx
"use client"
import * as React from "react"
import type { DateRange } from "react-day-picker"
import { DownloadIcon, TrendingDownIcon, TrendingUpIcon, WalletIcon } from "@/components/icons"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import { ControlSizeProvider } from "@/components/ui/control-size"
import { DateRangePicker } from "@/components/ui/date-range-picker"
import {
EmptyState,
EmptyStateActions,
EmptyStateDescription,
EmptyStateTitle,
} from "@/components/ui/empty-state"
import { SearchField } from "@/components/ui/search-field"
import {
StatTile,
StatTileContent,
StatTileHeader,
StatTileIcon,
StatTileLabel,
StatTileSegments,
StatTileSparkline,
StatTileTrend,
StatTileUnit,
StatTileValue,
} from "@/components/ui/stat-tile"
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group"
import { formatNumber, formatJalaliDateTime } from "@/lib/persian"
type Kind = "deposit" | "withdraw"
const ROWS: { id: string; title: string; date: string; amount: number; kind: Kind; ok: boolean }[] = [
{ id: "t1", title: "واریز حقوق", date: "2026-09-29T08:00:00", amount: 38_000_000, kind: "deposit", ok: true },
{ id: "t2", title: "کارت به کارت به کیان مرادی", date: "2026-09-28T19:30:00", amount: 2_500_000, kind: "withdraw", ok: true },
{ id: "t3", title: "پرداخت قبض برق", date: "2026-09-27T11:10:00", amount: 386_000, kind: "withdraw", ok: true },
{ id: "t4", title: "خرید اینترنتی", date: "2026-09-26T22:45:00", amount: 1_240_000, kind: "withdraw", ok: false },
{ id: "t5", title: "واریز از حساب پسانداز", date: "2026-09-25T09:00:00", amount: 5_000_000, kind: "deposit", ok: true },
]
export default function TransactionsBlock() {
const [query, setQuery] = React.useState("")
const [kind, setKind] = React.useState("all")
const [range, setRange] = React.useState<DateRange>()
const rows = ROWS.filter((row) => {
const date = new Date(row.date)
return (
(kind === "all" || row.kind === kind) &&
row.title.includes(query) &&
(!range?.from || date >= range.from) &&
(!range?.to || date <= new Date(range.to.getTime() + 86_400_000))
)
})
const sum = (k: Kind) => rows.filter((row) => row.kind === k && row.ok).reduce((total, row) => total + row.amount, 0)
return (
<ControlSizeProvider size="xl" asChild>
<main className="mx-auto flex w-full min-w-0 max-w-5xl flex-col gap-6 p-4 sm:p-6">
<header className="flex items-start justify-between gap-4 sm:items-end">
<div className="flex min-w-0 flex-col gap-1">
<h1 className="text-2xl font-bold">گردش حساب</h1>
<p className="text-sm text-muted-foreground">واریز و برداشتهای حساب جاری را جستوجو و فیلتر کنید.</p>
</div>
<Button variant="outline" aria-label="خروجی اکسل" className="shrink-0">
<DownloadIcon />
<span className="hidden sm:inline">خروجی اکسل</span>
</Button>
</header>
{/* سه کارتِ آمار: واریز و برداشت با روند، مانده با نوارِ سهمِ هر کدام. */}
<div className="grid grid-cols-2 gap-3 sm:gap-4 lg:grid-cols-3">
<StatTile className="gap-3 p-4 sm:p-5">
<StatTileHeader>
<StatTileIcon tone="success">
<TrendingUpIcon />
</StatTileIcon>
<StatTileLabel>مجموع واریز</StatTileLabel>
</StatTileHeader>
<StatTileContent>
<StatTileValue className="whitespace-nowrap text-xl sm:text-2xl">
{formatNumber(sum("deposit"))}
<StatTileUnit>تومان</StatTileUnit>
</StatTileValue>
<StatTileTrend value={8.4} formatValue={() => "+۸٫۴٪"} className="max-sm:hidden" />
</StatTileContent>
<StatTileSparkline values={[22, 25, 24, 30, 28, 35, 43]} className="h-10" />
</StatTile>
<StatTile className="gap-3 p-4 sm:p-5">
<StatTileHeader>
<StatTileIcon tone="destructive">
<TrendingDownIcon />
</StatTileIcon>
<StatTileLabel>مجموع برداشت</StatTileLabel>
</StatTileHeader>
<StatTileContent>
<StatTileValue className="whitespace-nowrap text-xl sm:text-2xl">
{formatNumber(sum("withdraw"))}
<StatTileUnit>تومان</StatTileUnit>
</StatTileValue>
{/* کاهشِ برداشت خبرِ خوبی است؛ رنگ برعکس. */}
<StatTileTrend value={-3.2} invertColor formatValue={() => "−۳٫۲٪"} className="max-sm:hidden" />
</StatTileContent>
<StatTileSparkline values={[4.1, 3.6, 3.9, 3.2, 3.4, 2.9, 2.9]} color="var(--destructive)" className="h-10" />
</StatTile>
<StatTile className="col-span-2 gap-3 p-4 sm:p-5 lg:col-span-1">
<StatTileHeader>
<StatTileIcon tone="accent">
<WalletIcon />
</StatTileIcon>
<StatTileLabel>مانده در این بازه</StatTileLabel>
</StatTileHeader>
<StatTileValue className="whitespace-nowrap text-xl sm:text-2xl">
{formatNumber(sum("deposit") - sum("withdraw"))}
<StatTileUnit>تومان</StatTileUnit>
</StatTileValue>
<StatTileSegments
segments={[
{ value: sum("deposit"), color: "var(--success)" },
{ value: Math.max(1, sum("withdraw")), color: "var(--destructive)" },
]}
/>
<div className="flex justify-between text-xs text-muted-foreground">
<span className="flex items-center gap-1.5">
<span aria-hidden className="size-2 rounded-full bg-success" />
واریز
</span>
<span className="flex items-center gap-1.5">
<span aria-hidden className="size-2 rounded-full bg-destructive" />
برداشت
</span>
</div>
</StatTile>
</div>
{/* جدول و فیلترهایش یک سطحاند: نوارِ ابزار بالای همان کارت، نه ردیفِ جدا. */}
{/* ابزارها بیرونِ قاب، ۳۲ پیکسل بالای جدول؛ فقط فهرست/جدول قاب دارد. */}
<section aria-label="فهرست تراکنشها" className="mt-6 flex flex-col gap-8">
<ControlSizeProvider size="xl">
{/* سه فیلترِ همخانواده با فاصلهٔ برابر (۱۲). */}
<div className="flex flex-col gap-9 md:flex-row md:items-center md:gap-3">
{/* فیلترِ تکانتخابی با سگمنت؛ در موبایل تمامعرض با سه بخشِ مساوی. */}
<ToggleGroup type="single" variant="segmented" value={kind} onValueChange={(value) => value && setKind(value as string)} aria-label="نوع تراکنش" className="w-full md:w-auto">
<ToggleGroupItem value="all" className="flex-1 px-4">همه</ToggleGroupItem>
<ToggleGroupItem value="deposit" className="flex-1 px-4">واریز</ToggleGroupItem>
<ToggleGroupItem value="withdraw" className="flex-1 px-4">برداشت</ToggleGroupItem>
</ToggleGroup>
<div className="flex gap-3 md:ms-auto">
<SearchField
aria-label="جستوجوی تراکنش"
placeholder="جستوجو در شرح"
value={query}
onValueChange={setQuery}
className="min-w-0 flex-1 md:w-60 md:flex-none"
/>
<DateRangePicker
calendar="jalali"
value={range}
onChange={setRange}
aria-label="بازهٔ تاریخ"
className="w-36 shrink-0 sm:w-56"
/>
</div>
</div>
</ControlSizeProvider>
{rows.length === 0 ? (
<EmptyState variant="bordered" className="py-12">
<EmptyStateTitle>تراکنشی در این بازه نیست</EmptyStateTitle>
<EmptyStateDescription>بازهٔ تاریخ یا فیلترها را تغییر دهید.</EmptyStateDescription>
<EmptyStateActions>
<Button
variant="outline"
onClick={() => {
setQuery("")
setKind("all")
setRange(undefined)
}}
>
پاککردن فیلترها
</Button>
</EmptyStateActions>
</EmptyState>
) : (
<>
{/* موبایل: فهرستِ دوخطی بهجای جدولِ اسکرولخور؛ مبلغ همیشه دیده میشود. */}
<ul className="divide-y divide-border overflow-hidden rounded-xl border border-border bg-card md:hidden">
{rows.map((row) => (
<li key={row.id} className="flex items-center justify-between gap-3 px-4 py-3.5">
<div className="flex min-w-0 flex-col gap-3">
<span className="truncate font-medium">{row.title}</span>
<span className="text-xs text-muted-foreground">{formatJalaliDateTime(row.date)}</span>
</div>
<div className="flex shrink-0 flex-col items-end gap-1">
<span className={row.kind === "deposit" ? "font-semibold text-success-subtle-foreground" : "font-semibold"}>
<bdi dir="ltr">
{row.kind === "deposit" ? "+" : "−"}
{formatNumber(row.amount)}
</bdi>
</span>
{!row.ok && (
<Badge variant="soft" color="destructive" size="sm">
ناموفق
</Badge>
)}
</div>
</li>
))}
</ul>
<div className="hidden rounded-xl border border-border bg-card md:block">
<Table>
<TableHeader className="bg-muted">
<TableRow>
<TableHead>شرح</TableHead>
<TableHead>تاریخ و ساعت</TableHead>
<TableHead>وضعیت</TableHead>
<TableHead align="end">مبلغ</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{rows.map((row) => (
<TableRow key={row.id}>
<TableCell className="font-medium">{row.title}</TableCell>
<TableCell className="whitespace-nowrap text-muted-foreground">{formatJalaliDateTime(row.date)}</TableCell>
<TableCell>
<Badge variant="soft" color={row.ok ? "success" : "destructive"}>
{row.ok ? "موفق" : "ناموفق"}
</Badge>
</TableCell>
<TableCell
align="end"
className={row.kind === "deposit" ? "whitespace-nowrap font-medium text-success-subtle-foreground" : "whitespace-nowrap font-medium"}
>
<bdi dir="ltr">
{row.kind === "deposit" ? "+" : "−"}
{formatNumber(row.amount)}
</bdi>{" "}
تومان
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
</>
)}
</section>
</main>
</ControlSizeProvider>
)
}