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

248 lines
7.7 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 {
addDays,
differenceInDays,
format as formatDate,
startOfDay,
subDays,
} from "date-fns";
import { faIR } from "date-fns/locale";
import { CalendarIcon } from "lucide-react";
import { useMemo, useState } from "react";
import type { DateRange } from "react-day-picker";
import { Area, AreaChart, CartesianGrid, XAxis, YAxis } from "recharts";
import { Button } from "@/components/ui/button";
import { Calendar } from "@/components/ui/calendar";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import {
type ChartConfig,
ChartContainer,
ChartTooltip,
ChartTooltipContent,
} from "@/components/ui/chart";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { cn } from "@/lib/utils";
interface ChartGroup11Props {
className?: string;
}
const seededRandom = (seed: number) => {
const x = Math.sin(seed) * 10000;
return x - Math.floor(x);
};
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 numberFormatter = new Intl.NumberFormat("fa-IR", {
maximumFractionDigits: 0,
});
const generateDataForRange = (from: Date, to: Date) => {
const data = [];
const days = differenceInDays(to, from) + 1;
for (let i = 0; i < days; i++) {
const date = addDays(startOfDay(from), i);
const seed = date.getTime() / 86400000;
data.push({
date: date,
dateStr: formatDate(date, "d MMM", { locale: faIR }),
revenue: Math.floor(seededRandom(seed) * 5000) + 2000,
orders: Math.floor(seededRandom(seed + 1000) * 50) + 20,
});
}
return data;
};
const chartConfig = {
revenue: { label: "درآمد", color: "var(--chart-1)" },
} satisfies ChartConfig;
const ChartGroup11 = ({ className }: ChartGroup11Props) => {
const [dateRange, setDateRange] = useState<DateRange | undefined>({
from: subDays(new Date(), 30),
to: new Date(),
});
const filteredData = useMemo(() => {
const from = dateRange?.from ?? subDays(new Date(), 30);
const to = dateRange?.to ?? new Date();
return generateDataForRange(from, to);
}, [dateRange]);
const totalRevenue = filteredData.reduce(
(sum, item) => sum + item.revenue,
0,
);
const totalOrders = filteredData.reduce((sum, item) => sum + item.orders, 0);
const avgOrderValue =
totalOrders > 0 ? Math.round(totalRevenue / totalOrders) : 0;
return (
<section
dir="rtl"
lang="fa"
className={cn("py-32 text-start", className)}
>
<div className="container">
<Card className="w-full text-start">
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-4">
<div>
<CardTitle>تحلیل درآمد</CardTitle>
<CardDescription>
پیگیری عملکرد درآمد در طول زمان
</CardDescription>
</div>
<Popover>
<PopoverTrigger asChild>
<Button
variant="outline"
className="min-w-[280px] justify-start text-start font-normal"
>
<CalendarIcon className="ms-2 size-4" aria-hidden="true" />
{dateRange?.from ? (
dateRange.to ? (
<>
{formatDate(dateRange.from, "d MMM y", {
locale: faIR,
})}{" "}
–{" "}
{formatDate(dateRange.to, "d MMM y", { locale: faIR })}
</>
) : (
formatDate(dateRange.from, "d MMM y", { locale: faIR })
)
) : (
<span>انتخاب بازه تاریخ</span>
)}
</Button>
</PopoverTrigger>
<PopoverContent className="w-auto p-0" align="start">
<Calendar
mode="range"
numberOfMonths={2}
locale={faIR}
selected={dateRange}
onSelect={setDateRange}
defaultMonth={subDays(new Date(), 30)}
/>
</PopoverContent>
</Popover>
</CardHeader>
<CardContent>
<div className="mb-6 grid grid-cols-3 gap-4">
<div className="rounded-lg border p-4">
<p className="text-sm text-muted-foreground">مجموع درآمد</p>
<p className="text-xl font-bold tabular-nums">
{currencyFormatter.format(totalRevenue)}
</p>
</div>
<div className="rounded-lg border p-4">
<p className="text-sm text-muted-foreground">مجموع سفارش‌ها</p>
<p className="text-xl font-bold tabular-nums">
{numberFormatter.format(totalOrders)}
</p>
</div>
<div className="rounded-lg border p-4">
<p className="text-sm text-muted-foreground">
میانگین ارزش سفارش
</p>
<p className="text-xl font-bold tabular-nums">
{currencyFormatter.format(avgOrderValue)}
</p>
</div>
</div>
<ChartContainer config={chartConfig} className="h-72 w-full">
<AreaChart
data={filteredData}
margin={{ top: 10, right: 10, left: 0, bottom: 0 }}
>
<defs>
<linearGradient id="revGrad11" x1="0" y1="0" x2="0" y2="1">
<stop
offset="0%"
stopColor="var(--color-revenue)"
stopOpacity={0.3}
/>
<stop
offset="100%"
stopColor="var(--color-revenue)"
stopOpacity={0}
/>
</linearGradient>
</defs>
<CartesianGrid strokeDasharray="3 3" vertical={false} />
<XAxis
dataKey="dateStr"
axisLine={false}
tickLine={false}
tickMargin={8}
fontSize={12}
interval="preserveStartEnd"
/>
<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="revenue"
stroke="var(--color-revenue)"
strokeWidth={2}
fill="url(#revGrad11)"
/>
</AreaChart>
</ChartContainer>
</CardContent>
</Card>
</div>
</section>
);
};
export { ChartGroup11 };