From c7b4e774215018773b2c8464e926f00464add13b Mon Sep 17 00:00:00 2001 From: hamid zarghami Date: Wed, 1 Jul 2026 12:14:00 +0330 Subject: [PATCH] fix chart --- .../statistics/components/RevenueChart.tsx | 163 +++++++----------- 1 file changed, 67 insertions(+), 96 deletions(-) diff --git a/src/pages/statistics/components/RevenueChart.tsx b/src/pages/statistics/components/RevenueChart.tsx index 9277d6b..d7bf850 100644 --- a/src/pages/statistics/components/RevenueChart.tsx +++ b/src/pages/statistics/components/RevenueChart.tsx @@ -5,11 +5,45 @@ import { formatPrice } from "@/helpers/func"; import { ArrowDown, ArrowUp, DocumentDownload } from "iconsax-react"; import moment from "moment-jalaali"; import { type FC, useMemo, useState } from "react"; -import { Area, AreaChart, CartesianGrid, ResponsiveContainer, Tooltip, XAxis, YAxis } from "recharts"; +import { CartesianGrid, Line, LineChart, Tooltip, XAxis, YAxis } from "recharts"; import { useGetPaymentsChart } from "../hooks/useStatsData"; +const PERSIAN_MONTHS = [ + "فروردین", + "اردیبهشت", + "خرداد", + "تیر", + "مرداد", + "شهریور", + "مهر", + "آبان", + "آذر", + "دی", + "بهمن", + "اسفند", +] as const; + +const toPersianDigits = (value: string | number): string => + String(value).replace(/\d/g, (d) => "۰۱۲۳۴۵۶۷۸۹"[Number(d)]); + +const formatJalaliAxisLabel = (dateString: string, period: "daily" | "monthly" | "yearly"): string => { + const m = moment(dateString, "YYYY-MM-DD"); + const jYear = m.jYear(); + const jMonth = m.jMonth(); + const jDate = m.jDate(); + + if (period === "yearly") return toPersianDigits(jYear); + if (period === "monthly") return `${PERSIAN_MONTHS[jMonth]} ${toPersianDigits(jYear)}`; + return toPersianDigits(`${String(jMonth + 1).padStart(2, "0")}/${String(jDate).padStart(2, "0")}`); +}; + +const formatJalaliFullDate = (dateString: string): string => { + const m = moment(dateString, "YYYY-MM-DD"); + return `${toPersianDigits(m.jDate())} ${PERSIAN_MONTHS[m.jMonth()]} ${toPersianDigits(m.jYear())}`; +}; + const RevenueChart: FC = () => { - const [selectedTab] = useState<"daily" | "monthly" | "yearly">("monthly"); + const [selectedTab] = useState<"daily" | "monthly" | "yearly">("daily"); const [startDate, setStartDate] = useState(""); const [endDate, setEndDate] = useState(""); @@ -33,38 +67,20 @@ const RevenueChart: FC = () => { const { data: paymentsChartData, isLoading } = useGetPaymentsChart(apiParams); - const formatDateLabel = (dateString: string, period: "daily" | "monthly" | "yearly"): string => { - const date = new Date(dateString); - const options: Intl.DateTimeFormatOptions = { - year: "numeric", - }; - if (period === "monthly") { - options.month = "long"; - } else if (period === "daily") { - options.month = "short"; - options.day = "numeric"; - } - const persianDate = new Intl.DateTimeFormat("fa-IR", options).format(date); - return persianDate; - }; - const chartData = useMemo(() => { if (!paymentsChartData?.data) return []; - return paymentsChartData.data.map((item) => ({ - month: formatDateLabel(item.date, selectedTab), - online: item.online, - cash: item.cash, - })); + return [...paymentsChartData.data] + .sort((a, b) => moment(a.date, "YYYY-MM-DD").valueOf() - moment(b.date, "YYYY-MM-DD").valueOf()) + .map((item) => ({ + date: item.date, + label: formatJalaliAxisLabel(item.date, selectedTab), + tooltipLabel: formatJalaliFullDate(item.date), + online: item.online, + cash: item.cash, + })); }, [paymentsChartData?.data, selectedTab]); - const xAxisInterval = useMemo(() => { - const dataLength = chartData.length; - if (dataLength <= 7) return 0; - if (dataLength <= 15) return 1; - if (dataLength <= 30) return 2; - if (dataLength <= 60) return 4; - return Math.floor(dataLength / 12); - }, [chartData.length]); + const chartWidth = useMemo(() => Math.max(chartData.length * 48, 640), [chartData.length]); const totalRevenue = useMemo(() => { return chartData.reduce((sum, item) => sum + item.online + item.cash, 0); @@ -94,49 +110,16 @@ const RevenueChart: FC = () => { const CustomTooltip = (props: { active?: boolean; payload?: Array<{ - dataKey?: string; - value?: number; - color?: string; payload?: { - cash?: number; - online?: number; - month?: string; + cash: number; + online: number; + tooltipLabel: string; }; }>; - label?: string; }) => { - const { active, payload, label } = props; - if (!active || !payload || payload.length === 0 || !label) { - return null; - } - - // استفاده از paymentsChartData.data مستقیم برای دریافت داده‌های واقعی - // پیدا کردن آخرین آیتمی که label آن با formatDateLabel match می‌کند - let dataPoint: { cash: number; online: number } | null = null; - - if (paymentsChartData?.data) { - // پیدا کردن همه آیتم‌های match شده و استفاده از آخرین - const matchingItems: Array<{ cash: number; online: number }> = []; - for (const item of paymentsChartData.data) { - const formattedLabel = formatDateLabel(item.date, selectedTab); - if (formattedLabel === label) { - matchingItems.push({ cash: item.cash, online: item.online }); - } - } - if (matchingItems.length > 0) { - dataPoint = matchingItems[matchingItems.length - 1]; - } - } - - // اگر پیدا نکردیم، از chartData استفاده می‌کنیم - if (!dataPoint) { - const matchingItems = chartData.filter((item) => item.month === label); - if (matchingItems.length > 0) { - dataPoint = matchingItems[matchingItems.length - 1]; - } - } - - if (!dataPoint) { + const { active, payload } = props; + const dataPoint = payload?.[0]?.payload; + if (!active || !dataPoint) { return null; } @@ -145,7 +128,7 @@ const RevenueChart: FC = () => { return (
-

{label}

+

{dataPoint.tooltipLabel}

@@ -248,38 +231,26 @@ const RevenueChart: FC = () => { ) : chartData.length === 0 ? (
داده‌ای برای نمایش وجود ندارد
) : ( -
- - - - - - - - - - - - +
+
+ 15 ? -45 : 0} - textAnchor={chartData.length > 15 ? "end" : "middle"} - height={chartData.length > 15 ? 60 : 40} - minTickGap={10} + tick={{ fill: "#9CA3AF", fontSize: 10 }} + interval={0} + angle={-45} + textAnchor="end" + height={72} /> - } /> - - - - + } cursor={{ stroke: "#E5E7EB", strokeWidth: 1 }} /> + + + +
)}