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

271 lines
8.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"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 };