"use client"; import { addDays, differenceInDays, format as formatDate, startOfDay, subDays, } from "date-fns"; import { faIR } from "date-fns/locale"; import { ArrowDownRight, ArrowUpRight, CalendarIcon, DollarSign, Eye, ShoppingCart, Users, } from "lucide-react"; import { useMemo, useState } from "react"; import type { DateRange } from "react-day-picker"; import { Area, AreaChart, CartesianGrid, Line, LineChart, 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 { ScrollArea } from "@/components/ui/scroll-area"; import { cn } from "@/lib/utils"; interface ChartGroup13Props { 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 percentChangeFormatter = new Intl.NumberFormat("fa-IR", { maximumFractionDigits: 1, minimumFractionDigits: 1, signDisplay: "exceptZero", }); 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) * 12000) + 5000, users: Math.floor(seededRandom(seed + 1000) * 500) + 200, orders: Math.floor(seededRandom(seed + 2000) * 100) + 40, pageviews: Math.floor(seededRandom(seed + 3000) * 5000) + 2000, }); } return data; }; const presets = [ { label: "امروز", getValue: () => ({ from: new Date(), to: new Date() }) }, { label: "دیروز", getValue: () => ({ from: subDays(new Date(), 1), to: subDays(new Date(), 1), }), }, { label: "۷ روز اخیر", getValue: () => ({ from: subDays(new Date(), 6), to: new Date() }), }, { label: "۱۴ روز اخیر", getValue: () => ({ from: subDays(new Date(), 13), to: new Date() }), }, { label: "۳۰ روز اخیر", getValue: () => ({ from: subDays(new Date(), 29), to: new Date() }), }, { label: "۹۰ روز اخیر", getValue: () => ({ from: subDays(new Date(), 89), to: new Date() }), }, ]; const revenueConfig = { revenue: { label: "درآمد", color: "var(--chart-1)" }, } satisfies ChartConfig; const usersConfig = { users: { label: "کاربران", color: "var(--chart-2)" }, } satisfies ChartConfig; const ordersConfig = { orders: { label: "سفارش‌ها", color: "var(--chart-3)" }, } satisfies ChartConfig; const pageviewsConfig = { pageviews: { label: "بازدید صفحه", color: "var(--chart-4)" }, } satisfies ChartConfig; const ChartGroup13 = ({ className }: ChartGroup13Props) => { const [dateRange, setDateRange] = useState({ from: subDays(new Date(), 29), to: new Date(), }); const [presetLabel, setPresetLabel] = useState("۳۰ روز اخیر"); const filteredData = useMemo(() => { const from = dateRange?.from ?? subDays(new Date(), 29); const to = dateRange?.to ?? new Date(); return generateDataForRange(from, to); }, [dateRange]); const stats = useMemo(() => { const totalRevenue = filteredData.reduce( (sum, item) => sum + item.revenue, 0, ); const totalUsers = filteredData.reduce((sum, item) => sum + item.users, 0); const totalOrders = filteredData.reduce( (sum, item) => sum + item.orders, 0, ); const totalPageviews = filteredData.reduce( (sum, item) => sum + item.pageviews, 0, ); return { totalRevenue, totalUsers, totalOrders, totalPageviews }; }, [filteredData]); const handlePreset = (preset: (typeof presets)[0]) => { setDateRange(preset.getValue()); setPresetLabel(preset.label); }; const handleCustomSelect = (range: DateRange | undefined) => { setDateRange(range); setPresetLabel("بازه دلخواه"); }; const dateRangeSubtitle = dateRange?.from && dateRange?.to ? `${formatDate(dateRange.from, "d MMM y", { locale: faIR })} – ${formatDate(dateRange.to, "d MMM y", { locale: faIR })}` : "انتخاب بازه تاریخ"; const statCards = [ { title: "درآمد", value: currencyFormatter.format(stats.totalRevenue), change: 12.5, icon: DollarSign, config: revenueConfig, dataKey: "revenue" as const, color: "var(--chart-1)", }, { title: "کاربران", value: numberFormatter.format(stats.totalUsers), change: 8.2, icon: Users, config: usersConfig, dataKey: "users" as const, color: "var(--chart-2)", }, { title: "سفارش‌ها", value: numberFormatter.format(stats.totalOrders), change: -3.1, icon: ShoppingCart, config: ordersConfig, dataKey: "orders" as const, color: "var(--chart-3)", }, { title: "بازدید صفحه", value: numberFormatter.format(stats.totalPageviews), change: 15.8, icon: Eye, config: pageviewsConfig, dataKey: "pageviews" as const, color: "var(--chart-4)", }, ]; return (

تحلیل‌ها

{dateRangeSubtitle}

{presets.map((preset) => ( ))}
{statCards.map((stat, index) => ( {stat.title}
{stat.value}
{stat.change >= 0 ? ( ) : ( )} = 0 ? "text-green-500" : "text-red-500", "tabular-nums", )} > {percentChangeFormatter.format(stat.change)}٪ نسبت به دوره قبل
))}
درآمد در طول زمان درآمد روزانه در بازه انتخاب‌شده compactCurrencyFormatter.format(Number(v)) } /> currencyFormatter.format(Number(value)) } /> } cursor={{ stroke: "var(--border)", strokeDasharray: "4 4" }} /> کاربران و سفارش‌ها همبستگی بین کاربران و سفارش‌ها numberFormatter.format(Number(v))} /> numberFormatter.format(Number(v))} /> numberFormatter.format(Number(value)) } /> } cursor={{ stroke: "var(--border)", strokeDasharray: "4 4" }} />
); }; export { ChartGroup13 };