mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-30 02:23:47 +00:00
- Move shared chart components (DailyChart, PeriodComparison, StatCard) to src/components/stats/ with configurable label props - Create shared types for chart data decoupled from partner API - Add campaign chart data API client with deposits/spending split - Enhance AdminCampaignStats with daily trends chart, period comparison, top registrations, and deposit/spending stat cards - Add useChartColors hook with SSR guard and theme-aware CSS variable parsing - Add cross-platform clipboard utility with execCommand fallback for Telegram WebView - Add i18n keys for chart analytics across all 4 locales (en, ru, zh, fa) - Responsive fixes: truncation for long names/values, responsive text sizing, touch-friendly copy buttons
311 lines
8.2 KiB
TypeScript
311 lines
8.2 KiB
TypeScript
import apiClient from './client';
|
|
|
|
// Types
|
|
export type CampaignBonusType = 'balance' | 'subscription' | 'none' | 'tariff';
|
|
|
|
export interface TariffInfo {
|
|
id: number;
|
|
name: string;
|
|
}
|
|
|
|
export interface CampaignListItem {
|
|
id: number;
|
|
name: string;
|
|
start_parameter: string;
|
|
bonus_type: CampaignBonusType;
|
|
is_active: boolean;
|
|
registrations_count: number;
|
|
total_revenue_kopeks: number;
|
|
conversion_rate: number;
|
|
partner_user_id: number | null;
|
|
partner_name: string | null;
|
|
created_at: string;
|
|
}
|
|
|
|
export interface CampaignListResponse {
|
|
campaigns: CampaignListItem[];
|
|
total: number;
|
|
}
|
|
|
|
export interface CampaignDetail {
|
|
id: number;
|
|
name: string;
|
|
start_parameter: string;
|
|
bonus_type: CampaignBonusType;
|
|
is_active: boolean;
|
|
balance_bonus_kopeks: number;
|
|
balance_bonus_rubles: number;
|
|
subscription_duration_days: number | null;
|
|
subscription_traffic_gb: number | null;
|
|
subscription_device_limit: number | null;
|
|
subscription_squads: string[];
|
|
tariff_id: number | null;
|
|
tariff_duration_days: number | null;
|
|
tariff: TariffInfo | null;
|
|
partner_user_id: number | null;
|
|
partner_name: string | null;
|
|
created_by: number | null;
|
|
created_at: string;
|
|
updated_at: string | null;
|
|
deep_link: string | null;
|
|
web_link: string | null;
|
|
}
|
|
|
|
export interface CampaignCreateRequest {
|
|
name: string;
|
|
start_parameter: string;
|
|
bonus_type: CampaignBonusType;
|
|
is_active?: boolean;
|
|
balance_bonus_kopeks?: number;
|
|
subscription_duration_days?: number;
|
|
subscription_traffic_gb?: number;
|
|
subscription_device_limit?: number;
|
|
subscription_squads?: string[];
|
|
tariff_id?: number;
|
|
tariff_duration_days?: number;
|
|
partner_user_id?: number | null;
|
|
}
|
|
|
|
export interface CampaignUpdateRequest {
|
|
name?: string;
|
|
start_parameter?: string;
|
|
bonus_type?: CampaignBonusType;
|
|
is_active?: boolean;
|
|
balance_bonus_kopeks?: number;
|
|
subscription_duration_days?: number;
|
|
subscription_traffic_gb?: number;
|
|
subscription_device_limit?: number;
|
|
subscription_squads?: string[];
|
|
tariff_id?: number;
|
|
tariff_duration_days?: number;
|
|
partner_user_id?: number | null;
|
|
}
|
|
|
|
export interface CampaignToggleResponse {
|
|
id: number;
|
|
is_active: boolean;
|
|
message: string;
|
|
}
|
|
|
|
export interface CampaignStatistics {
|
|
id: number;
|
|
name: string;
|
|
start_parameter: string;
|
|
bonus_type: CampaignBonusType;
|
|
is_active: boolean;
|
|
registrations: number;
|
|
balance_issued_kopeks: number;
|
|
balance_issued_rubles: number;
|
|
subscription_issued: number;
|
|
last_registration: string | null;
|
|
total_revenue_kopeks: number;
|
|
total_revenue_rubles: number;
|
|
avg_revenue_per_user_kopeks: number;
|
|
avg_revenue_per_user_rubles: number;
|
|
avg_first_payment_kopeks: number;
|
|
avg_first_payment_rubles: number;
|
|
trial_users_count: number;
|
|
active_trials_count: number;
|
|
conversion_count: number;
|
|
paid_users_count: number;
|
|
conversion_rate: number;
|
|
trial_conversion_rate: number;
|
|
deep_link: string | null;
|
|
web_link: string | null;
|
|
}
|
|
|
|
export interface CampaignRegistrationItem {
|
|
id: number;
|
|
user_id: number;
|
|
telegram_id: number;
|
|
username: string | null;
|
|
first_name: string | null;
|
|
bonus_type: string;
|
|
balance_bonus_kopeks: number;
|
|
subscription_duration_days: number | null;
|
|
tariff_id: number | null;
|
|
tariff_duration_days: number | null;
|
|
created_at: string;
|
|
user_balance_kopeks: number;
|
|
has_subscription: boolean;
|
|
has_paid: boolean;
|
|
}
|
|
|
|
export interface CampaignRegistrationsResponse {
|
|
registrations: CampaignRegistrationItem[];
|
|
total: number;
|
|
page: number;
|
|
per_page: number;
|
|
}
|
|
|
|
export interface CampaignsOverview {
|
|
total: number;
|
|
active: number;
|
|
inactive: number;
|
|
total_registrations: number;
|
|
total_balance_issued_kopeks: number;
|
|
total_balance_issued_rubles: number;
|
|
total_subscription_issued: number;
|
|
total_tariff_issued: number;
|
|
}
|
|
|
|
export interface AdminDailyStatItem {
|
|
date: string;
|
|
referrals_count: number;
|
|
earnings_kopeks: number;
|
|
}
|
|
|
|
export interface AdminPeriodStats {
|
|
days: number;
|
|
referrals_count: number;
|
|
earnings_kopeks: number;
|
|
}
|
|
|
|
export interface AdminPeriodChange {
|
|
absolute: number;
|
|
percent: number;
|
|
trend: 'up' | 'down' | 'stable';
|
|
}
|
|
|
|
export interface AdminPeriodComparison {
|
|
current: AdminPeriodStats;
|
|
previous: AdminPeriodStats;
|
|
referrals_change: AdminPeriodChange;
|
|
earnings_change: AdminPeriodChange;
|
|
}
|
|
|
|
export interface AdminTopRegistrationItem {
|
|
id: number;
|
|
full_name: string;
|
|
created_at: string;
|
|
has_paid: boolean;
|
|
is_active: boolean;
|
|
total_earnings_kopeks: number;
|
|
}
|
|
|
|
export interface AdminCampaignChartData {
|
|
campaign_id: number;
|
|
total_deposits_kopeks: number;
|
|
total_spending_kopeks: number;
|
|
daily_stats: AdminDailyStatItem[];
|
|
period_comparison: AdminPeriodComparison;
|
|
top_registrations: AdminTopRegistrationItem[];
|
|
}
|
|
|
|
export interface ServerSquadInfo {
|
|
id: number;
|
|
squad_uuid: string;
|
|
display_name: string;
|
|
country_code: string | null;
|
|
}
|
|
|
|
export interface TariffListItem {
|
|
id: number;
|
|
name: string;
|
|
description: string | null;
|
|
is_active: boolean;
|
|
traffic_limit_gb: number;
|
|
device_limit: number;
|
|
}
|
|
|
|
export interface AvailablePartner {
|
|
user_id: number;
|
|
username: string | null;
|
|
first_name: string | null;
|
|
}
|
|
|
|
export const campaignsApi = {
|
|
// Get campaigns overview
|
|
getOverview: async (): Promise<CampaignsOverview> => {
|
|
const response = await apiClient.get('/cabinet/admin/campaigns/overview');
|
|
return response.data;
|
|
},
|
|
|
|
// Get all campaigns
|
|
getCampaigns: async (
|
|
includeInactive = true,
|
|
offset = 0,
|
|
limit = 50,
|
|
): Promise<CampaignListResponse> => {
|
|
const response = await apiClient.get('/cabinet/admin/campaigns', {
|
|
params: { include_inactive: includeInactive, offset, limit },
|
|
});
|
|
return response.data;
|
|
},
|
|
|
|
// Get single campaign
|
|
getCampaign: async (campaignId: number): Promise<CampaignDetail> => {
|
|
const response = await apiClient.get(`/cabinet/admin/campaigns/${campaignId}`);
|
|
return response.data;
|
|
},
|
|
|
|
// Get campaign statistics
|
|
getCampaignStats: async (campaignId: number): Promise<CampaignStatistics> => {
|
|
const response = await apiClient.get(`/cabinet/admin/campaigns/${campaignId}/stats`);
|
|
return response.data;
|
|
},
|
|
|
|
// Get campaign registrations
|
|
getCampaignRegistrations: async (
|
|
campaignId: number,
|
|
page = 1,
|
|
perPage = 50,
|
|
): Promise<CampaignRegistrationsResponse> => {
|
|
const response = await apiClient.get(`/cabinet/admin/campaigns/${campaignId}/registrations`, {
|
|
params: { page, per_page: perPage },
|
|
});
|
|
return response.data;
|
|
},
|
|
|
|
// Create campaign
|
|
createCampaign: async (data: CampaignCreateRequest): Promise<CampaignDetail> => {
|
|
const response = await apiClient.post('/cabinet/admin/campaigns', data);
|
|
return response.data;
|
|
},
|
|
|
|
// Update campaign
|
|
updateCampaign: async (
|
|
campaignId: number,
|
|
data: CampaignUpdateRequest,
|
|
): Promise<CampaignDetail> => {
|
|
const response = await apiClient.put(`/cabinet/admin/campaigns/${campaignId}`, data);
|
|
return response.data;
|
|
},
|
|
|
|
// Delete campaign
|
|
deleteCampaign: async (campaignId: number): Promise<{ message: string }> => {
|
|
const response = await apiClient.delete(`/cabinet/admin/campaigns/${campaignId}`);
|
|
return response.data;
|
|
},
|
|
|
|
// Toggle campaign active status
|
|
toggleCampaign: async (campaignId: number): Promise<CampaignToggleResponse> => {
|
|
const response = await apiClient.post(`/cabinet/admin/campaigns/${campaignId}/toggle`);
|
|
return response.data;
|
|
},
|
|
|
|
// Get available servers for subscription bonus
|
|
getAvailableServers: async (): Promise<ServerSquadInfo[]> => {
|
|
const response = await apiClient.get('/cabinet/admin/campaigns/available-servers');
|
|
return response.data;
|
|
},
|
|
|
|
// Get available tariffs for tariff bonus
|
|
getAvailableTariffs: async (): Promise<TariffListItem[]> => {
|
|
const response = await apiClient.get('/cabinet/admin/campaigns/available-tariffs');
|
|
return response.data;
|
|
},
|
|
|
|
// Get available partners for campaign assignment
|
|
getAvailablePartners: async (): Promise<AvailablePartner[]> => {
|
|
const response = await apiClient.get('/cabinet/admin/campaigns/available-partners');
|
|
return response.data;
|
|
},
|
|
|
|
// Get campaign chart data for analytics
|
|
getChartData: async (campaignId: number): Promise<AdminCampaignChartData> => {
|
|
const response = await apiClient.get(`/cabinet/admin/campaigns/${campaignId}/chart-data`);
|
|
return response.data;
|
|
},
|
|
};
|