import { useState } from 'react'; import { useNavigate, useParams } from 'react-router'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { useTranslation } from 'react-i18next'; import i18n from '../i18n'; import { couponsApi } from '../api/coupons'; import { usePlatform } from '../platform/hooks/usePlatform'; import { copyToClipboard } from '../utils/clipboard'; import { formatPrice, formatShortDate } from '../utils/format'; import { getApiErrorMessage } from '../utils/api-error'; import { useToast } from '../components/Toast'; import { PermissionGate } from '../components/auth/PermissionGate'; import { BackIcon, CheckIcon, CheckCircleIcon, ChartBarIcon, CopyIcon, DownloadIcon, TicketIcon, } from '@/components/icons'; import { StatCard } from '../components/stats'; export default function AdminCouponDetail() { const { t } = useTranslation(); const navigate = useNavigate(); const { id } = useParams<{ id: string }>(); const queryClient = useQueryClient(); const { capabilities } = usePlatform(); const { showToast } = useToast(); const batchId = Number(id); const [revokeConfirm, setRevokeConfirm] = useState(false); const [copied, setCopied] = useState(false); const { data: batch, isLoading } = useQuery({ queryKey: ['admin-coupon-batch', batchId], queryFn: () => couponsApi.getBatch(batchId), enabled: Number.isFinite(batchId), }); const hasActive = (batch?.active_count ?? 0) > 0; const { data: links } = useQuery({ queryKey: ['admin-coupon-batch-links', batchId], queryFn: () => couponsApi.getBatchLinks(batchId), enabled: Number.isFinite(batchId) && hasActive, }); const revokeMutation = useMutation({ mutationFn: () => couponsApi.revokeBatch(batchId), onSuccess: (result) => { queryClient.invalidateQueries({ queryKey: ['admin-coupon-batch', batchId] }); queryClient.invalidateQueries({ queryKey: ['admin-coupon-batch-links', batchId] }); queryClient.invalidateQueries({ queryKey: ['admin-coupon-batches'] }); setRevokeConfirm(false); showToast({ type: 'success', title: t('admin.coupons.revoke.success', { count: result.revoked_count }), message: result.batch.name, }); }, onError: (err) => { setRevokeConfirm(false); showToast({ type: 'error', title: t('admin.coupons.errors.revokeFailed'), message: getApiErrorMessage(err, ''), }); }, }); const handleCopyAll = () => { if (!links) return; void copyToClipboard(links.links.join('\n')); setCopied(true); setTimeout(() => setCopied(false), 2000); }; const handleDownload = () => { if (!links) return; const blob = new Blob([links.links.join('\n') + '\n'], { type: 'text/plain;charset=utf-8' }); const url = URL.createObjectURL(blob); const anchor = document.createElement('a'); anchor.href = url; anchor.download = `coupons_batch_${batchId}.txt`; anchor.click(); URL.revokeObjectURL(url); }; if (isLoading) { return (
); } if (!batch) { return (

{t('admin.coupons.errors.loadFailed')}

); } return (
{/* Header */}
{!capabilities.hasBackButton && ( )}

{t('admin.coupons.detail.title', { id: batch.id })} · {batch.name}

{batch.tariff_name || '—'} · {batch.period_days} {t('admin.coupons.days')} {batch.is_revoked && ( {t('admin.coupons.list.revoked')} )}

{/* Stats */}
} tone="success" /> } tone="accent" /> } tone="warning" />
{/* Info card */}
{t('admin.coupons.detail.total')} {batch.coupons_total}
{batch.wholesale_price_kopeks > 0 && (
{t('admin.coupons.detail.price')} {formatPrice(batch.wholesale_price_kopeks, i18n.language)} ·{' '} {t('admin.coupons.detail.priceTotal')}{' '} {formatPrice(batch.wholesale_price_kopeks * batch.coupons_total, i18n.language)}
)}
{t('admin.coupons.detail.validUntil')} {batch.valid_until ? formatShortDate(batch.valid_until) : t('admin.coupons.detail.perpetual')}
{t('admin.coupons.detail.createdAt')} {formatShortDate(batch.created_at)}
{/* Links */} {hasActive && links && (
{t('admin.coupons.detail.linksTitle', { count: links.count })}