feat: display per-campaign stats on partner detail page

- Add registrations_count, referrals_count, earnings_kopeks to campaign type
- Show per-campaign stats grid (registrations, referrals, earnings) under each campaign card
- Use conditional coloring for zero earnings (neutral instead of green)
This commit is contained in:
Fringg
2026-03-02 01:09:55 +03:00
parent a933f661e4
commit 75a6149e2d
2 changed files with 73 additions and 35 deletions

View File

@@ -105,7 +105,15 @@ export interface AdminPartnerDetailResponse {
earnings_week: number; earnings_week: number;
earnings_month: number; earnings_month: number;
conversion_to_paid: number; conversion_to_paid: number;
campaigns: { id: number; name: string; start_parameter: string; is_active: boolean }[]; campaigns: {
id: number;
name: string;
start_parameter: string;
is_active: boolean;
registrations_count: number;
referrals_count: number;
earnings_kopeks: number;
}[];
created_at: string; created_at: string;
} }

View File

@@ -232,46 +232,76 @@ export default function AdminPartnerDetail() {
{partner.campaigns.map((campaign) => ( {partner.campaigns.map((campaign) => (
<div <div
key={campaign.id} key={campaign.id}
className={`flex items-center justify-between rounded-lg bg-dark-700/50 p-3 ${ className={`rounded-lg bg-dark-700/50 p-3 ${
!campaign.is_active ? 'opacity-60' : '' !campaign.is_active ? 'opacity-60' : ''
}`} }`}
> >
<div className="min-w-0 flex-1"> <div className="flex items-center justify-between">
<div className="font-medium text-dark-100">{campaign.name}</div> <div className="min-w-0 flex-1">
<div className="font-mono text-xs text-dark-500"> <div className="font-medium text-dark-100">{campaign.name}</div>
?start={campaign.start_parameter} <div className="font-mono text-xs text-dark-500">
?start={campaign.start_parameter}
</div>
</div>
<div className="flex items-center gap-2">
{campaign.is_active ? (
<span className="rounded bg-success-500/20 px-2 py-0.5 text-xs text-success-400">
{t('admin.partnerDetail.campaigns.active')}
</span>
) : (
<span className="rounded bg-dark-600 px-2 py-0.5 text-xs text-dark-400">
{t('admin.partnerDetail.campaigns.inactive')}
</span>
)}
<button
onClick={() => unassignMutation.mutate(campaign.id)}
disabled={unassignMutation.isPending}
className="rounded p-1 text-dark-500 transition-colors hover:bg-error-500/10 hover:text-error-400"
title={t('admin.partnerDetail.campaigns.unassign')}
>
<svg
className="h-4 w-4"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
strokeWidth={2}
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M6 18L18 6M6 6l12 12"
/>
</svg>
</button>
</div> </div>
</div> </div>
<div className="flex items-center gap-2"> <div className="mt-2 grid grid-cols-3 gap-2 border-t border-dark-600/50 pt-2">
{campaign.is_active ? ( <div className="text-center">
<span className="rounded bg-success-500/20 px-2 py-0.5 text-xs text-success-400"> <div className="text-sm font-medium text-dark-200">
{t('admin.partnerDetail.campaigns.active')} {campaign.registrations_count}
</span> </div>
) : ( <div className="text-[10px] text-dark-500">
<span className="rounded bg-dark-600 px-2 py-0.5 text-xs text-dark-400"> {t('admin.partnerDetail.campaigns.registrations', 'Регистрации')}
{t('admin.partnerDetail.campaigns.inactive')} </div>
</span> </div>
)} <div className="text-center">
<button <div className="text-sm font-medium text-dark-200">
onClick={() => unassignMutation.mutate(campaign.id)} {campaign.referrals_count}
disabled={unassignMutation.isPending} </div>
className="rounded p-1 text-dark-500 transition-colors hover:bg-error-500/10 hover:text-error-400" <div className="text-[10px] text-dark-500">
title={t('admin.partnerDetail.campaigns.unassign')} {t('admin.partnerDetail.campaigns.referrals', 'Рефералы')}
> </div>
<svg </div>
className="h-4 w-4" <div className="text-center">
fill="none" <div
viewBox="0 0 24 24" className={`text-sm font-medium ${campaign.earnings_kopeks > 0 ? 'text-success-400' : 'text-dark-400'}`}
stroke="currentColor"
strokeWidth={2}
> >
<path {formatWithCurrency(campaign.earnings_kopeks / 100)}
strokeLinecap="round" </div>
strokeLinejoin="round" <div className="text-[10px] text-dark-500">
d="M6 18L18 6M6 6l12 12" {t('admin.partnerDetail.campaigns.earnings', 'Доход')}
/> </div>
</svg> </div>
</button>
</div> </div>
</div> </div>
))} ))}