feat: add daily traffic & device purchase chart to addons stats

Add DualAreaChart to addons tab showing daily traffic purchases vs
device purchases with date-union merge. Add daily_devices field to
AddonsStats type and i18n keys for all 4 locales.
This commit is contained in:
Fringg
2026-03-02 21:57:18 +03:00
parent 4622b4b200
commit 2235b3cb77
6 changed files with 38 additions and 14 deletions

View File

@@ -119,6 +119,11 @@ export interface DailyAddonItem {
total_gb: number;
}
export interface DailyDeviceItem {
date: string;
count: number;
}
export interface AddonsStats {
total_purchases: number;
total_gb_purchased: number;
@@ -127,6 +132,7 @@ export interface AddonsStats {
device_revenue_kopeks: number;
by_package: AddonByPackageItem[];
daily: DailyAddonItem[];
daily_devices: DailyDeviceItem[];
}
// ============ Deposits ============

View File

@@ -1,3 +1,4 @@
import { useMemo } from 'react';
import { useQuery } from '@tanstack/react-query';
import { useTranslation } from 'react-i18next';
@@ -7,7 +8,7 @@ import { SALES_STATS } from '../../constants/salesStats';
import { useCurrency } from '../../hooks/useCurrency';
import { StatCard } from '../stats';
import { SimpleAreaChart } from './SimpleAreaChart';
import { DualAreaChart } from './DualAreaChart';
import { SimpleBarChart } from './SimpleBarChart';
interface AddonsTabProps {
@@ -24,6 +25,18 @@ export function AddonsTab({ params }: AddonsTabProps) {
staleTime: SALES_STATS.STALE_TIME,
});
const dailyChartData = useMemo(() => {
if (!data) return [];
const trafficByDate = new Map(data.daily.map((d) => [d.date, d.count]));
const deviceByDate = new Map(data.daily_devices.map((d) => [d.date, d.count]));
const allDates = Array.from(new Set([...trafficByDate.keys(), ...deviceByDate.keys()])).sort();
return allDates.map((date) => ({
date,
series1: trafficByDate.get(date) ?? 0,
series2: deviceByDate.get(date) ?? 0,
}));
}, [data]);
if (isLoading) {
return (
<div className="animate-pulse space-y-4">
@@ -43,11 +56,6 @@ export function AddonsTab({ params }: AddonsTabProps) {
value: item.count,
}));
const dailyData = data.daily.map((item) => ({
date: item.date,
value: item.count,
}));
return (
<div className="space-y-4">
<div className="grid grid-cols-2 gap-3 sm:grid-cols-3 lg:grid-cols-5">
@@ -77,12 +85,14 @@ export function AddonsTab({ params }: AddonsTabProps) {
<div className="grid grid-cols-1 gap-4 lg:grid-cols-2">
<SimpleBarChart data={packageBarData} title={t('admin.salesStats.addons.byPackage')} />
<SimpleAreaChart
data={dailyData}
<DualAreaChart
data={dailyChartData}
title={t('admin.salesStats.addons.dailyChart')}
chartId="addons-daily"
valueLabel={t('admin.salesStats.addons.purchases')}
color={SALES_STATS.BAR_COLORS[5]}
series1Label={t('admin.salesStats.addons.trafficPurchases')}
series2Label={t('admin.salesStats.addons.devicePurchasesDaily')}
series1Color={SALES_STATS.BAR_COLORS[5]}
series2Color={SALES_STATS.BAR_COLORS[3]}
/>
</div>
</div>

View File

@@ -1140,8 +1140,10 @@
"revenue": "Revenue",
"devicePurchases": "Device Purchases",
"deviceRevenue": "Device Revenue",
"trafficPurchases": "Traffic",
"devicePurchasesDaily": "Devices",
"byPackage": "By Package Size",
"dailyChart": "Daily Purchases",
"dailyChart": "Daily Add-on Purchases",
"purchases": "Purchases"
},
"deposits": {

View File

@@ -963,8 +963,10 @@
"revenue": "درآمد",
"devicePurchases": "خرید دستگاه",
"deviceRevenue": "درآمد دستگاه",
"trafficPurchases": "ترافیک",
"devicePurchasesDaily": "دستگاه",
"byPackage": "بر اساس بسته",
"dailyChart": "خرید روزانه",
"dailyChart": "خرید روزانه خدمات اضافی",
"purchases": "خرید"
},
"deposits": {

View File

@@ -1161,8 +1161,10 @@
"revenue": "Доход",
"devicePurchases": "Покупки устройств",
"deviceRevenue": "Доход от устройств",
"trafficPurchases": "Трафик",
"devicePurchasesDaily": "Устройства",
"byPackage": "По размеру пакета",
"dailyChart": "Покупки по дням",
"dailyChart": "Покупки доп. услуг по дням",
"purchases": "Покупки"
},
"deposits": {

View File

@@ -963,8 +963,10 @@
"revenue": "收入",
"devicePurchases": "设备购买",
"deviceRevenue": "设备收入",
"trafficPurchases": "流量",
"devicePurchasesDaily": "设备",
"byPackage": "按套餐大小",
"dailyChart": "每日购买",
"dailyChart": "每日附加服务购买",
"purchases": "购买"
},
"deposits": {