import { useState } from 'react'; import { useNavigate } from 'react-router'; import { useInfiniteQuery, useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { useTranslation } from 'react-i18next'; import i18n from '../i18n'; import { campaignsApi, CampaignListItem, CampaignBonusType } from '../api/campaigns'; import { PlusIcon, EditIcon, TrashIcon, CheckIcon, XIcon, ChartIcon } from '../components/icons'; import { usePlatform } from '../platform/hooks/usePlatform'; import { useFocusTrap } from '../hooks/useFocusTrap'; const PAGE_SIZE = 50; // Bonus type labels and colors 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-accent-400', bgColor: 'bg-accent-500/20', }, none: { labelKey: 'admin.campaigns.bonusType.none', color: 'text-dark-400', bgColor: 'bg-dark-500/20', }, }; // Icons const BackIcon = () => ( ); // Locale mapping for formatting const localeMap: Record = { ru: 'ru-RU', en: 'en-US', zh: 'zh-CN', fa: 'fa-IR' }; // Format number as rubles const formatRubles = (kopeks: number) => { const locale = localeMap[i18n.language] || 'ru-RU'; return ( (kopeks / 100).toLocaleString(locale, { minimumFractionDigits: 0, maximumFractionDigits: 2 }) + ' ₽' ); }; // Main Component export default function AdminCampaigns() { const { t } = useTranslation(); const navigate = useNavigate(); const queryClient = useQueryClient(); const { capabilities } = usePlatform(); const [deleteConfirm, setDeleteConfirm] = useState(null); const deleteDialogRef = useFocusTrap(deleteConfirm !== null, { onEscape: () => setDeleteConfirm(null), }); // Queries const { data: campaignsData, isLoading, hasNextPage, fetchNextPage, isFetchingNextPage, } = useInfiniteQuery({ queryKey: ['admin-campaigns'], queryFn: ({ pageParam = 0 }) => campaignsApi.getCampaigns(true, pageParam, PAGE_SIZE), initialPageParam: 0, getNextPageParam: (lastPage, allPages) => { const loaded = allPages.reduce((sum, p) => sum + p.campaigns.length, 0); return loaded < lastPage.total ? loaded : undefined; }, }); const { data: overview } = useQuery({ queryKey: ['admin-campaigns-overview'], queryFn: () => campaignsApi.getOverview(), }); // Mutations const deleteMutation = useMutation({ mutationFn: campaignsApi.deleteCampaign, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['admin-campaigns'] }); queryClient.invalidateQueries({ queryKey: ['admin-campaigns-overview'] }); setDeleteConfirm(null); }, }); const toggleMutation = useMutation({ mutationFn: campaignsApi.toggleCampaign, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['admin-campaigns'] }); }, }); const campaigns = campaignsData?.pages.flatMap((p) => p.campaigns) ?? []; return (
{/* Header */}
{/* Show back button only on web, not in Telegram Mini App */} {!capabilities.hasBackButton && ( )}

{t('admin.campaigns.title')}

{t('admin.campaigns.subtitle')}

{/* Overview */} {overview && (
{overview.total}
{t('admin.campaigns.overview.totalCampaigns')}
{overview.active}
{t('admin.campaigns.overview.active')}
{overview.total_registrations}
{t('admin.campaigns.overview.registrations')}
{formatRubles(overview.total_balance_issued_kopeks)}
{t('admin.campaigns.overview.bonusesIssued')}
)} {/* Campaigns List */} {isLoading ? (
) : campaigns.length === 0 ? (

{t('admin.campaigns.noData')}

) : (
{campaigns.map((campaign: CampaignListItem) => (

{campaign.name}

{t(bonusTypeConfig[campaign.bonus_type].labelKey)} {campaign.partner_name && ( {campaign.partner_name} )} {!campaign.is_active && ( {t('admin.campaigns.table.inactive')} )}
?start={campaign.start_parameter} {t('admin.campaigns.table.registrations', { count: campaign.registrations_count, })} {t('admin.campaigns.table.revenue', { amount: formatRubles(campaign.total_revenue_kopeks), })} {t('admin.campaigns.table.conversion', { rate: campaign.conversion_rate })}
{/* Stats */} {/* Toggle Active */} {/* Edit */} {/* Delete */}
))} {/* Load more */} {hasNextPage && ( )}
)} {/* Delete Confirmation */} {deleteConfirm !== null && (
setDeleteConfirm(null)} aria-hidden="true" />

{t('admin.campaigns.confirm.deleteTitle')}

{t('admin.campaigns.confirm.deleteText')}

)}
); }