Files
reui-blocks/blocks/chart-group3.tsx
T

271 lines
8.1 KiB
TypeScript
Raw Normal View History

"use client";
import { TrendingDown, TrendingUp } from "lucide-react";
import {
Area,
AreaChart,
CartesianGrid,
Line,
LineChart,
ResponsiveContainer,
XAxis,
YAxis,
} from "recharts";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import {
type ChartConfig,
ChartContainer,
ChartTooltip,
ChartTooltipContent,
} from "@/components/ui/chart";
import { cn } from "@/lib/utils";
interface Stat {
title: string;
value: string;
change: number;
sparklineData: { day: string; value: number }[];
}
interface ChartGroup3Props {
stats?: Stat[];
className?: string;
}
const monthLabel = (monthIndex: number) =>
new Intl.DateTimeFormat("fa-IR", { month: "short" }).format(
new Date(2025, monthIndex, 1),
);
const weekdayFormatter = new Intl.DateTimeFormat("fa-IR", {
weekday: "short",
});
const weekdaySparkline = (values: number[]) =>
values.map((value, index) => ({
day: weekdayFormatter.format(new Date(2025, 0, 6 + index)),
value,
}));
const currencyFormatter = new Intl.NumberFormat("fa-IR", {
style: "currency",
currency: "IRR",
minimumFractionDigits: 0,
maximumFractionDigits: 0,
});
const compactCurrencyFormatter = new Intl.NumberFormat("fa-IR", {
style: "currency",
currency: "IRR",
notation: "compact",
minimumFractionDigits: 0,
maximumFractionDigits: 1,
});
const oneDecimalFormatter = new Intl.NumberFormat("fa-IR", {
maximumFractionDigits: 1,
minimumFractionDigits: 1,
});
const percentChangeFormatter = new Intl.NumberFormat("fa-IR", {
maximumFractionDigits: 1,
minimumFractionDigits: 1,
signDisplay: "exceptZero",
});
const formatPercent = (value: number) =>
`${oneDecimalFormatter.format(value)}٪`;
const defaultStats: Stat[] = [
{
title: "مجموع درآمد",
value: currencyFormatter.format(45231),
change: 20.1,
sparklineData: weekdaySparkline([6200, 5800, 7100, 6900, 8200, 5400, 5631]),
},
{
title: "میانگین ارزش سفارش",
value: currencyFormatter.format(142),
change: 8.5,
sparklineData: weekdaySparkline([128, 135, 142, 138, 155, 148, 142]),
},
{
title: "نرخ تبدیل",
value: formatPercent(3.2),
change: -2.4,
sparklineData: weekdaySparkline([3.8, 3.5, 3.4, 3.6, 3.1, 2.9, 3.2]),
},
];
const chartData = [
{ month: monthLabel(0), value: 18600 },
{ month: monthLabel(1), value: 30500 },
{ month: monthLabel(2), value: 23700 },
{ month: monthLabel(3), value: 27300 },
{ month: monthLabel(4), value: 20900 },
{ month: monthLabel(5), value: 31400 },
];
const chartConfig = {
value: { label: "درآمد", color: "var(--chart-1)" },
} satisfies ChartConfig;
const ChartGroup3 = ({ stats = defaultStats, className }: ChartGroup3Props) => {
return (
<section
dir="rtl"
lang="fa"
className={cn("py-32 text-start", className)}
>
<div className="container space-y-4">
<Card className="text-start">
<CardHeader className="pb-2">
<CardTitle>روند درآمد</CardTitle>
<CardDescription>درآمد ماهانه در طول زمان</CardDescription>
</CardHeader>
<CardContent className="pb-4">
<ChartContainer config={chartConfig} className="h-64 w-full">
<AreaChart
data={chartData}
margin={{ top: 10, right: 10, left: 0, bottom: 0 }}
>
<defs>
<linearGradient
id="chartGradient3"
x1="0"
y1="0"
x2="0"
y2="1"
>
<stop
offset="0%"
stopColor="var(--color-value)"
stopOpacity={0.3}
/>
<stop
offset="100%"
stopColor="var(--color-value)"
stopOpacity={0}
/>
</linearGradient>
</defs>
<CartesianGrid strokeDasharray="3 3" vertical={false} />
<XAxis
dataKey="month"
axisLine={false}
tickLine={false}
tickMargin={8}
fontSize={12}
/>
<YAxis
axisLine={false}
tickLine={false}
tickMargin={8}
fontSize={10}
width={52}
tickFormatter={(v) =>
compactCurrencyFormatter.format(Number(v))
}
/>
<ChartTooltip
content={
<ChartTooltipContent
formatter={(value) =>
currencyFormatter.format(Number(value))
}
/>
}
cursor={{ stroke: "var(--border)", strokeDasharray: "4 4" }}
/>
<Area
type="monotone"
dataKey="value"
stroke="var(--color-value)"
strokeWidth={2}
fill="url(#chartGradient3)"
/>
</AreaChart>
</ChartContainer>
</CardContent>
</Card>
<div className="grid gap-4 md:grid-cols-3">
{stats.map((stat, index) => {
const isPositive = stat.change >= 0;
const sparklineColor = isPositive ? "#22c55e" : "#ef4444";
return (
<Card key={index} className="overflow-hidden text-start">
<CardHeader className="pb-0">
<CardDescription>۷ روز گذشته</CardDescription>
<div className="flex items-center justify-between gap-2">
<CardTitle className="text-base">{stat.title}</CardTitle>
<div className="flex items-center gap-2">
<span className="text-xl font-bold tabular-nums">
{stat.value}
</span>
<div className="flex items-center gap-1 text-sm tabular-nums">
{isPositive ? (
<TrendingUp
className="size-4 text-green-500"
aria-hidden="true"
/>
) : (
<TrendingDown
className="size-4 text-red-500"
aria-hidden="true"
/>
)}
<span
className={
isPositive ? "text-green-500" : "text-red-500"
}
>
{percentChangeFormatter.format(stat.change)}٪
</span>
</div>
</div>
</div>
</CardHeader>
<CardContent className="pb-0">
<div className="h-16 w-full">
<ResponsiveContainer width="100%" height="100%">
<LineChart
data={stat.sparklineData}
margin={{ top: 8, right: 0, left: 0, bottom: 0 }}
>
<XAxis
dataKey="day"
axisLine={false}
tickLine={false}
tick={{ fontSize: 10 }}
tickMargin={4}
/>
<Line
type="monotone"
dataKey="value"
stroke={sparklineColor}
strokeWidth={2}
dot={false}
/>
</LineChart>
</ResponsiveContainer>
</div>
</CardContent>
</Card>
);
})}
</div>
</div>
</section>
);
};
export { ChartGroup3 };