From b155c0b959e7c3583c473d5721dbc52f2ab21b32 Mon Sep 17 00:00:00 2001 From: hamid zarghami Date: Mon, 29 Jun 2026 14:54:56 +0330 Subject: [PATCH] revenuechart --- .../statistics/components/RevenueChart.tsx | 601 +++++++----------- 1 file changed, 239 insertions(+), 362 deletions(-) diff --git a/src/pages/statistics/components/RevenueChart.tsx b/src/pages/statistics/components/RevenueChart.tsx index be59296..9277d6b 100644 --- a/src/pages/statistics/components/RevenueChart.tsx +++ b/src/pages/statistics/components/RevenueChart.tsx @@ -1,224 +1,192 @@ -import { ArrowUp, DocumentDownload, ArrowDown } from 'iconsax-react' -import { type FC, useState, useMemo } from 'react' -import { - Area, - AreaChart, - CartesianGrid, - ResponsiveContainer, - Tooltip, - XAxis, - YAxis -} from 'recharts' -import DatePicker from '@/components/DatePicker' -import Button from '@/components/Button' -import { useGetPaymentsChart } from '../hooks/useStatsData' -import { formatPrice } from '@/helpers/func' -import PageLoading from '@/components/PageLoading' -import moment from 'moment-jalaali' +import Button from "@/components/Button"; +import DatePicker from "@/components/DatePicker"; +import PageLoading from "@/components/PageLoading"; +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 { useGetPaymentsChart } from "../hooks/useStatsData"; const RevenueChart: FC = () => { - const [selectedTab] = useState< - 'daily' | 'monthly' | 'yearly' - >('monthly') - const [startDate, setStartDate] = useState('') - const [endDate, setEndDate] = useState('') + const [selectedTab] = useState<"daily" | "monthly" | "yearly">("monthly"); + const [startDate, setStartDate] = useState(""); + const [endDate, setEndDate] = useState(""); - const convertToPersian = (gregorianDate: string | null | undefined): string | undefined => { - if (!gregorianDate) return undefined - const persianDate = moment(gregorianDate, 'YYYY-MM-DD').format('jYYYY-jMM-jDD') - return persianDate + const convertToPersian = (gregorianDate: string | null | undefined): string | undefined => { + if (!gregorianDate) return undefined; + const persianDate = moment(gregorianDate, "YYYY-MM-DD").format("jYYYY-jMM-jDD"); + return persianDate; + }; + + const apiParams = useMemo(() => { + const params: { + period?: "daily" | "monthly" | "yearly"; + startDate?: string; + endDate?: string; + } = {}; + // فقط اگر تاریخ انتخاب شده باشد ارسال شود + if (startDate && startDate.trim() !== "") params.startDate = startDate; + if (endDate && endDate.trim() !== "") params.endDate = endDate; + return params; + }, [startDate, endDate]); + + 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, + })); + }, [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 totalRevenue = useMemo(() => { + return chartData.reduce((sum, item) => sum + item.online + item.cash, 0); + }, [chartData]); + + const revenueChange = useMemo(() => { + if (chartData.length < 2) return null; + const currentPeriod = chartData[chartData.length - 1]; + const previousPeriod = chartData[chartData.length - 2]; + const currentTotal = currentPeriod.online + currentPeriod.cash; + const previousTotal = previousPeriod.online + previousPeriod.cash; + if (previousTotal === 0) return null; + const change = ((currentTotal - previousTotal) / previousTotal) * 100; + return change; + }, [chartData]); + + const formatYAxis = (value: number) => { + if (value === 0) return "۰"; + if (value >= 1000000) { + const millions = value / 1000000; + const formatted = millions.toFixed(1).replace(/\d/g, (d) => "۰۱۲۳۴۵۶۷۸۹"[parseInt(d)]); + return `${formatted} میلیون`; + } + return value.toLocaleString("fa-IR"); + }; + + const CustomTooltip = (props: { + active?: boolean; + payload?: Array<{ + dataKey?: string; + value?: number; + color?: string; + payload?: { + cash?: number; + online?: number; + month?: string; + }; + }>; + label?: string; + }) => { + const { active, payload, label } = props; + if (!active || !payload || payload.length === 0 || !label) { + return null; } - const apiParams = useMemo(() => { - const params: { - period?: 'daily' | 'monthly' | 'yearly'; - startDate?: string; - endDate?: string; - } = {} - // فقط اگر تاریخ انتخاب شده باشد ارسال شود - if (startDate && startDate.trim() !== '') params.startDate = startDate - if (endDate && endDate.trim() !== '') params.endDate = endDate - return params - }, [startDate, endDate]) + // استفاده از paymentsChartData.data مستقیم برای دریافت داده‌های واقعی + // پیدا کردن آخرین آیتمی که label آن با formatDateLabel match می‌کند + let dataPoint: { cash: number; online: number } | null = null; - 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 (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 (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 + } + if (matchingItems.length > 0) { + dataPoint = matchingItems[matchingItems.length - 1]; + } } - const chartData = useMemo(() => { - if (!paymentsChartData?.data) return [] - return paymentsChartData.data.map((item) => ({ - month: formatDateLabel(item.date, selectedTab), - 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 totalRevenue = useMemo(() => { - return chartData.reduce((sum, item) => sum + item.online + item.cash, 0) - }, [chartData]) - - const revenueChange = useMemo(() => { - if (chartData.length < 2) return null - const currentPeriod = chartData[chartData.length - 1] - const previousPeriod = chartData[chartData.length - 2] - const currentTotal = currentPeriod.online + currentPeriod.cash - const previousTotal = previousPeriod.online + previousPeriod.cash - if (previousTotal === 0) return null - const change = ((currentTotal - previousTotal) / previousTotal) * 100 - return change - }, [chartData]) - - const formatYAxis = (value: number) => { - if (value === 0) return '۰' - if (value >= 1000000) { - const millions = value / 1000000 - const formatted = millions - .toFixed(1) - .replace(/\d/g, (d) => '۰۱۲۳۴۵۶۷۸۹'[parseInt(d)]) - return `${formatted} میلیون` - } - return value.toLocaleString('fa-IR') + // اگر پیدا نکردیم، از chartData استفاده می‌کنیم + if (!dataPoint) { + const matchingItems = chartData.filter((item) => item.month === label); + if (matchingItems.length > 0) { + dataPoint = matchingItems[matchingItems.length - 1]; + } } - const CustomTooltip = (props: { - active?: boolean - payload?: Array<{ - dataKey?: string - value?: number - color?: string - payload?: { - cash?: number - online?: number - month?: 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) { - return null - } - - const cashValue = dataPoint.cash - const onlineValue = dataPoint.online - - return ( -
-

{label}

-
-
-
- پرداخت نقدی: - - {cashValue.toLocaleString('fa-IR')} تومان - -
-
-
- پرداخت آنلاین: - - {onlineValue.toLocaleString('fa-IR')} تومان - -
-
- جمع: - - {(cashValue + onlineValue).toLocaleString('fa-IR')} تومان - -
-
-
- ) + if (!dataPoint) { + return null; } + const cashValue = dataPoint.cash; + const onlineValue = dataPoint.online; + return ( -
- {/* Header */} -
-

مجموع درآمد

+
+

{label}

+
+
+
+ پرداخت نقدی: + {cashValue.toLocaleString("fa-IR")} تومان +
+
+
+ پرداخت آنلاین: + {onlineValue.toLocaleString("fa-IR")} تومان +
+
+ جمع: + {(cashValue + onlineValue).toLocaleString("fa-IR")} تومان +
+
+
+ ); + }; + return ( +
+ {/* Header */} +
+

مجموع درآمد

+ {formatPrice(totalRevenue)} +
-
+
+ + + +
-
- - - -
- - {/* Tabs */} - {/*
+ {/* Tabs */} + {/*
*/} - {/* مبلغ و درصد */} + {/* مبلغ و درصد */} -
- مجموع درآمد +
+
+ {revenueChange !== null && ( +
= 0 ? "bg-green-50" : "bg-red-50"}`}> + {revenueChange >= 0 ? : } + = 0 ? "text-green-600" : "text-red-600"}`}>{Math.abs(revenueChange).toFixed(1)}%
-
-
-

- {formatPrice(totalRevenue)} -

- {revenueChange !== null && ( -
= 0 - ? 'bg-green-50' - : 'bg-red-50' - }`} - > - {revenueChange >= 0 ? ( - - ) : ( - - )} - = 0 - ? 'text-green-600' - : 'text-red-600' - }`} - > - {Math.abs(revenueChange).toFixed(1)}% - -
- )} -
- -
-
-
- - پرداخت آنلاین - -
-
-
- - پرداخت نقدی - -
-
-
- - {/* نمودار */} - {isLoading ? ( -
- -
- ) : chartData.length === 0 ? ( -
- داده‌ای برای نمایش وجود ندارد -
- ) : ( -
- - - - - - - - - - - - - - 15 ? -45 : 0} - textAnchor={chartData.length > 15 ? 'end' : 'middle'} - height={chartData.length > 15 ? 60 : 40} - minTickGap={10} - /> - - } /> - - - - -
- )} + )}
- ) -} -export default RevenueChart +
+
+
+ پرداخت آنلاین +
+
+
+ پرداخت نقدی +
+
+
+ + {/* نمودار */} + {isLoading ? ( +
+ +
+ ) : chartData.length === 0 ? ( +
داده‌ای برای نمایش وجود ندارد
+ ) : ( +
+ + + + + + + + + + + + + + 15 ? -45 : 0} + textAnchor={chartData.length > 15 ? "end" : "middle"} + height={chartData.length > 15 ? 60 : 40} + minTickGap={10} + /> + + } /> + + + + +
+ )} +
+ ); +}; + +export default RevenueChart;