feat: add admin sales statistics dashboard with 5 analytics tabs

- Add sales stats API client with typed interfaces for all endpoints
- Implement AdminSalesStats page with summary cards and period selector
- Add TrialsTab with provider breakdown donut chart and daily area chart
- Add SalesTab with tariff/period breakdown and daily revenue chart
- Add RenewalsTab with period comparison and trend indicators
- Add AddonsTab with package breakdown bar chart
- Add DepositsTab with payment method breakdown bar chart
- Add SimpleBarChart, SimpleAreaChart, DonutChart components
- Add PeriodSelector with preset periods and custom date range
- Full responsive design (mobile/tablet/desktop/TG Mini App)
- ARIA accessibility (tablist, tab, tabpanel, aria-selected)
- Error handling with isError on all queries
- Empty data guards on all chart components
- i18n support across all 4 locales (en, ru, zh, fa)
This commit is contained in:
Fringg
2026-03-02 20:34:39 +03:00
parent 673de08dd4
commit a47c222310
13 changed files with 1226 additions and 0 deletions

175
src/api/adminSalesStats.ts Normal file
View File

@@ -0,0 +1,175 @@
import apiClient from './client';
// ============ Period Params ============
export interface SalesStatsParams {
days?: number;
start_date?: string;
end_date?: string;
}
// ============ Summary ============
export interface SalesSummary {
total_revenue_kopeks: number;
active_subscriptions: number;
active_trials: number;
new_trials: number;
trial_to_paid_conversion: number;
renewals_count: number;
addon_revenue_kopeks: number;
}
// ============ Trials ============
export interface ProviderBreakdownItem {
provider: string;
count: number;
}
export interface DailyTrialItem {
date: string;
count: number;
}
export interface TrialsStats {
total_trials: number;
conversion_rate: number;
avg_trial_duration_days: number;
by_provider: ProviderBreakdownItem[];
daily: DailyTrialItem[];
}
// ============ Sales ============
export interface SalesByTariffItem {
tariff_id: number;
tariff_name: string;
count: number;
}
export interface SalesByPeriodItem {
period_days: number;
count: number;
}
export interface DailySalesItem {
date: string;
count: number;
revenue_kopeks: number;
}
export interface SalesStats {
total_sales: number;
total_revenue_kopeks: number;
avg_order_kopeks: number;
top_tariff_name: string;
by_tariff: SalesByTariffItem[];
by_period: SalesByPeriodItem[];
daily: DailySalesItem[];
}
// ============ Renewals ============
export interface RenewalPeriodStats {
count: number;
revenue_kopeks: number;
}
export interface RenewalChange {
absolute: number;
percent: number;
trend: 'up' | 'down' | 'stable';
}
export interface DailyRenewalItem {
date: string;
count: number;
}
export interface RenewalsStats {
total_renewals: number;
total_revenue_kopeks: number;
renewal_rate: number;
current_period: RenewalPeriodStats;
previous_period: RenewalPeriodStats;
change: RenewalChange;
daily: DailyRenewalItem[];
}
// ============ Add-ons ============
export interface AddonByPackageItem {
traffic_gb: number;
count: number;
}
export interface DailyAddonItem {
date: string;
count: number;
total_gb: number;
}
export interface AddonsStats {
total_purchases: number;
total_gb_purchased: number;
addon_revenue_kopeks: number;
by_package: AddonByPackageItem[];
daily: DailyAddonItem[];
}
// ============ Deposits ============
export interface DepositByMethodItem {
method: string;
count: number;
amount_kopeks: number;
}
export interface DailyDepositItem {
date: string;
count: number;
amount_kopeks: number;
}
export interface DepositsStats {
total_deposits: number;
total_amount_kopeks: number;
avg_deposit_kopeks: number;
by_method: DepositByMethodItem[];
daily: DailyDepositItem[];
}
// ============ API ============
export const salesStatsApi = {
getSummary: async (params: SalesStatsParams = {}): Promise<SalesSummary> => {
const response = await apiClient.get('/cabinet/admin/stats/sales/summary', { params });
return response.data;
},
getTrials: async (params: SalesStatsParams = {}): Promise<TrialsStats> => {
const response = await apiClient.get('/cabinet/admin/stats/sales/trials', { params });
return response.data;
},
getSales: async (params: SalesStatsParams = {}): Promise<SalesStats> => {
const response = await apiClient.get('/cabinet/admin/stats/sales/subscriptions', { params });
return response.data;
},
getRenewals: async (params: SalesStatsParams = {}): Promise<RenewalsStats> => {
const response = await apiClient.get('/cabinet/admin/stats/sales/renewals', { params });
return response.data;
},
getAddons: async (params: SalesStatsParams = {}): Promise<AddonsStats> => {
const response = await apiClient.get('/cabinet/admin/stats/sales/addons', { params });
return response.data;
},
getDeposits: async (params: SalesStatsParams = {}): Promise<DepositsStats> => {
const response = await apiClient.get('/cabinet/admin/stats/sales/deposits', { params });
return response.data;
},
};