import { useEffect, useRef, useState } from 'react'; import { useParams, useNavigate, Link } from 'react-router'; import { useQuery } from '@tanstack/react-query'; import { useTranslation } from 'react-i18next'; import i18n from '../i18n'; import { campaignsApi, CampaignBonusType } from '../api/campaigns'; import { AdminBackButton } from '../components/admin'; // Icons const CopyIcon = () => ( ); const LinkIcon = () => ( ); const UsersIcon = () => ( ); const ChartIcon = () => ( ); // Bonus type config const bonusTypeConfig: Record< CampaignBonusType, { labelKey: string; color: string; bgColor: string } > = { balance: { labelKey: 'admin.campaigns.bonusType.balance', color: 'text-success-400', bgColor: 'bg-success-500/20', }, subscription: { labelKey: 'admin.campaigns.bonusType.subscription', color: 'text-accent-400', bgColor: 'bg-accent-500/20', }, tariff: { labelKey: 'admin.campaigns.bonusType.tariff', color: 'text-purple-400', bgColor: 'bg-purple-500/20', }, none: { labelKey: 'admin.campaigns.bonusType.none', color: 'text-dark-400', bgColor: 'bg-dark-600', }, }; // Helper functions const localeMap: Record = { ru: 'ru-RU', en: 'en-US', zh: 'zh-CN', fa: 'fa-IR' }; const formatRubles = (kopeks: number): string => { const rubles = kopeks / 100; const locale = localeMap[i18n.language] || 'ru-RU'; return `${rubles.toLocaleString(locale)} ₽`; }; const formatDate = (date: string | null): string => { if (!date) return '-'; const locale = localeMap[i18n.language] || 'ru-RU'; return new Date(date).toLocaleDateString(locale, { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit', }); }; export default function AdminCampaignStats() { const { t } = useTranslation(); const { id } = useParams<{ id: string }>(); const navigate = useNavigate(); const [copiedBot, setCopiedBot] = useState(false); const [copiedWeb, setCopiedWeb] = useState(false); const [showUsers, setShowUsers] = useState(false); const copyBotTimer = useRef>(undefined); const copyWebTimer = useRef>(undefined); useEffect(() => { return () => { clearTimeout(copyBotTimer.current); clearTimeout(copyWebTimer.current); }; }, []); // Fetch stats const { data: stats, isLoading, error, } = useQuery({ queryKey: ['campaign-stats', id], queryFn: () => campaignsApi.getCampaignStats(Number(id)), enabled: !!id, }); // Fetch registrations when users section is open const { data: registrationsData, isLoading: usersLoading } = useQuery({ queryKey: ['campaign-registrations', id], queryFn: () => campaignsApi.getCampaignRegistrations(Number(id), 1, 50), enabled: !!id && showUsers, }); const copyBotLink = async () => { if (stats?.deep_link) { try { await navigator.clipboard.writeText(stats.deep_link); setCopiedBot(true); clearTimeout(copyBotTimer.current); copyBotTimer.current = setTimeout(() => setCopiedBot(false), 2000); } catch { // Clipboard not available } } }; const copyWebLink = async () => { if (stats?.web_link) { try { await navigator.clipboard.writeText(stats.web_link); setCopiedWeb(true); clearTimeout(copyWebTimer.current); copyWebTimer.current = setTimeout(() => setCopiedWeb(false), 2000); } catch { // Clipboard not available } } }; if (isLoading) { return ( ); } if (error || !stats) { return ( {t('admin.campaigns.stats.title')} {t('admin.campaigns.stats.loadError')} navigate('/admin/campaigns')} className="mt-4 text-sm text-dark-400 hover:text-dark-200" > {t('common.back')} ); } return ( {/* Header */} {stats.name} {t(bonusTypeConfig[stats.bonus_type].labelKey)} {stats.is_active ? ( {t('admin.campaigns.stats.active')} ) : ( {t('admin.campaigns.stats.inactive')} )} {/* Links */} {(stats.deep_link || stats.web_link) && ( {stats.deep_link && ( {t('admin.campaigns.stats.botLink')} {stats.deep_link} {copiedBot ? t('admin.campaigns.stats.copied') : t('admin.campaigns.stats.copy')} )} {stats.web_link && ( {t('admin.campaigns.stats.webLink')} {stats.web_link} {copiedWeb ? t('admin.campaigns.stats.copied') : t('admin.campaigns.stats.copy')} )} )} {/* Main Stats */} {stats.registrations} {t('admin.campaigns.stats.registrations')} {formatRubles(stats.total_revenue_kopeks)} {t('admin.campaigns.stats.revenue')} {stats.paid_users_count} {t('admin.campaigns.stats.paidUsers')} {stats.conversion_rate}% {t('admin.campaigns.stats.conversion')} {/* Detailed Stats */} {t('admin.campaigns.stats.detailedStats')} {t('admin.campaigns.stats.bonusesIssued')} {stats.bonus_type === 'balance' && ( {formatRubles(stats.balance_issued_kopeks)} )} {stats.bonus_type === 'subscription' && ( {t('admin.campaigns.stats.subscriptionsIssued', { count: stats.subscription_issued, })} )} {stats.bonus_type === 'tariff' && ( {t('admin.campaigns.stats.tariffsIssued', { count: stats.subscription_issued })} )} {stats.bonus_type === 'none' && ( - )} {t('admin.campaigns.stats.avgRevenuePerUser')} {formatRubles(stats.avg_revenue_per_user_kopeks)} {t('admin.campaigns.stats.avgFirstPayment')} {formatRubles(stats.avg_first_payment_kopeks)} {t('admin.campaigns.stats.trialSubscriptions')} {t('admin.campaigns.stats.trialCount', { total: stats.trial_users_count, active: stats.active_trials_count, })} {t('admin.campaigns.stats.trialConversion')} {stats.trial_conversion_rate}% {t('admin.campaigns.stats.lastRegistration')} {formatDate(stats.last_registration)} {/* Users Section */} setShowUsers(!showUsers)} className="flex w-full items-center justify-between p-4" > {t('admin.campaigns.stats.users')} ({stats.registrations}) {showUsers && ( {usersLoading ? ( ) : registrationsData?.registrations.length === 0 ? ( {t('admin.campaigns.stats.noUsers')} ) : ( {registrationsData?.registrations.map((reg) => ( {reg.first_name || reg.username || `User #${reg.user_id}`} {reg.telegram_id} {reg.has_paid && ( {t('admin.campaigns.stats.paid')} )} {reg.has_subscription && ( {t('admin.campaigns.stats.hasSub')} )} {formatDate(reg.created_at)} ))} )} )} ); }
{t('admin.campaigns.stats.loadError')}