"use client"; import { addDays, differenceInDays, format as formatDate, startOfDay, subDays, } from "date-fns"; import { faIR } from "date-fns/locale"; import { CalendarIcon, ChevronLeft, ChevronRight as ChevronRightIcon, Download, MoreHorizontal, RefreshCw, Settings, TrendingDown, TrendingUp, } from "lucide-react"; import { useMemo, useState } from "react"; import type { DateRange } from "react-day-picker"; import { Area, AreaChart, Bar, BarChart, CartesianGrid, ComposedChart, Line, LineChart, Pie, PieChart, 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, ChartLegend, ChartLegendContent, ChartTooltip, ChartTooltipContent, } from "@/components/ui/chart"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { Popover, PopoverContent, PopoverTrigger, } from "@/components/ui/popover"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { cn } from "@/lib/utils"; interface ChartGroup15Props { 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 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 formatHour = (hour: number) => `${new Intl.NumberFormat("fa-IR", { minimumIntegerDigits: 2, useGrouping: false, }).format(hour)}:00`; const generateDataForRange = (from: Date, to: Date) => { const data = []; let currentDate = new Date(from); while (currentDate <= to) { const seed = currentDate.getTime() / 86400000; const baseRevenue = 10000 + Math.sin(seed / 7) * 3000; const baseCost = 6000 + Math.sin(seed / 7) * 1500; data.push({ date: new Date(currentDate), dateStr: formatDate(currentDate, "d MMM", { locale: faIR }), revenue: Math.floor(baseRevenue + seededRandom(seed * 1) * 2000), cost: Math.floor(baseCost + seededRandom(seed * 2) * 1000), profit: Math.floor( baseRevenue - baseCost + seededRandom(seed * 3) * 1000, ), orders: Math.floor(seededRandom(seed * 4) * 80) + 40, aov: Math.floor(seededRandom(seed * 5) * 50) + 100, newCustomers: Math.floor(seededRandom(seed * 6) * 30) + 10, returning: Math.floor(seededRandom(seed * 7) * 50) + 30, }); currentDate = addDays(currentDate, 1); } return data; }; const hourlyData = Array.from({ length: 24 }, (_, i) => { const seed = i * 1000; return { hour: formatHour(i), visitors: Math.floor(seededRandom(seed) * 500) + (i >= 9 && i <= 17 ? 300 : 50), conversions: Math.floor(seededRandom(seed + 1) * 30) + (i >= 9 && i <= 17 ? 15 : 2), }; }); const deviceData = [ { name: "دسکتاپ", value: 58, fill: "var(--chart-1)" }, { name: "موبایل", value: 32, fill: "var(--chart-2)" }, { name: "تبلت", value: 10, fill: "var(--chart-3)" }, ]; const deviceConfig = { دسکتاپ: { label: "دسکتاپ", color: "var(--chart-1)" }, موبایل: { label: "موبایل", color: "var(--chart-2)" }, تبلت: { label: "تبلت", color: "var(--chart-3)" }, } satisfies ChartConfig; const revenueConfig = { revenue: { label: "درآمد", color: "var(--chart-1)" }, cost: { label: "هزینه", color: "var(--chart-2)" }, profit: { label: "سود", color: "var(--chart-3)" }, orders: { label: "سفارش‌ها", color: "var(--chart-1)" }, } satisfies ChartConfig; const customerConfig = { newCustomers: { label: "جدید", color: "var(--chart-1)" }, returning: { label: "بازگشتی", color: "var(--chart-2)" }, } satisfies ChartConfig; const hourlyConfig = { visitors: { label: "بازدیدکنندگان", color: "var(--chart-1)" }, conversions: { label: "تبدیل‌ها", color: "var(--chart-3)" }, } satisfies ChartConfig; const ChartGroup15 = ({ className }: ChartGroup15Props) => { const [dateRange, setDateRange] = useState({ from: subDays(new Date(), 29), to: new Date(), }); const [chartView, setChartView] = useState<"revenue" | "profit" | "orders">( "revenue", ); const filteredData = useMemo(() => { if (!dateRange?.from) { return generateDataForRange(subDays(new Date(), 29), new Date()); } const from = startOfDay(dateRange.from); const to = dateRange.to ? startOfDay(dateRange.to) : from; return generateDataForRange(from, to); }, [dateRange]); const stats = useMemo(() => { const totalRevenue = filteredData.reduce( (sum, item) => sum + item.revenue, 0, ); const totalCost = filteredData.reduce((sum, item) => sum + item.cost, 0); const totalProfit = filteredData.reduce( (sum, item) => sum + item.profit, 0, ); const totalOrders = filteredData.reduce( (sum, item) => sum + item.orders, 0, ); const profitMargin = totalRevenue > 0 ? (totalProfit / totalRevenue) * 100 : 0; return { totalRevenue, totalCost, totalProfit, totalOrders, profitMargin, }; }, [filteredData]); const daysDiff = dateRange?.from && dateRange?.to ? differenceInDays(dateRange.to, dateRange.from) + 1 : 30; const navigateRange = (direction: "prev" | "next") => { if (!dateRange?.from || !dateRange?.to) return; const shift = direction === "prev" ? -daysDiff : daysDiff; setDateRange({ from: addDays(dateRange.from, shift), to: addDays(dateRange.to, shift), }); }; const periodSubtitle = dateRange?.from && dateRange?.to ? ` · ${formatDate(dateRange.from, "d MMM", { locale: faIR })} – ${formatDate(dateRange.to, "d MMM y", { locale: faIR })}` : ""; const statRows = [ { label: "درآمد", value: currencyFormatter.format(stats.totalRevenue), change: 12.5, }, { label: "هزینه", value: currencyFormatter.format(stats.totalCost), change: 8.2, }, { label: "سود", value: currencyFormatter.format(stats.totalProfit), change: 18.3, }, { label: "سفارش‌ها", value: numberFormatter.format(stats.totalOrders), change: 5.7, }, { label: "حاشیه سود", value: formatPercent(stats.profitMargin), change: 2.1, }, ]; const currencyTooltip = ( currencyFormatter.format(Number(value))} /> ); const numberTooltip = ( numberFormatter.format(Number(value))} /> ); return (

تحلیل کسب‌وکار

بازه {numberFormatter.format(daysDiff)} روزه انتخاب شده {periodSubtitle}

خروجی جدول به‌روزرسانی تنظیمات
{statRows.map((stat, index) => (

{stat.label}

{stat.value}

= 0 ? "text-green-600" : "text-red-600", )} > {stat.change >= 0 ? ( ) : ( )} {percentChangeFormatter.format(stat.change)}٪
))}
setChartView(v as typeof chartView)} dir="rtl" >
مرور عملکرد روند درآمد، هزینه و سود
درآمد سود سفارش‌ها
compactCurrencyFormatter.format(Number(v)) } /> } /> compactCurrencyFormatter.format(Number(v)) } /> numberFormatter.format(Number(v))} />
تفکیک دستگاه ترافیک بر اساس نوع دستگاه formatPercent(Number(value))} /> } />
{deviceData.map((item, index) => (
{item.name}
{formatPercent(item.value)}
))}
انواع مشتری مشتریان جدید در برابر بازگشتی numberFormatter.format(Number(v))} /> ترافیک ساعتی بازدیدکنندگان و تبدیل‌ها به تفکیک ساعت (امروز) numberFormatter.format(Number(v))} /> numberFormatter.format(Number(v))} />
); }; export { ChartGroup15 };