style(admin-landings): rebuild stats summary on the StatCard pattern

The landing stats page used plain, icon-less centered boxes that looked
inconsistent with the Sales/Remnawave stats. Rebuild the summary on the shared
StatCard (tinted icon chip + tone + value), consolidating the two scattered
stat rows into a clean 2×4 grid (created/paid/revenue/conversion +
purchases/regular/gifts/avg) with barrel Phosphor icons. Chart and list
containers now use the same bento-card surface as the rest of the admin stats.
This commit is contained in:
c0mrade
2026-06-03 14:50:28 +03:00
parent b9521f1aa7
commit 966c2bf03a

View File

@@ -26,12 +26,20 @@ import { useCurrency } from '../hooks/useCurrency';
import { useChartColors } from '../hooks/useChartColors'; import { useChartColors } from '../hooks/useChartColors';
import { CHART_COMMON } from '../constants/charts'; import { CHART_COMMON } from '../constants/charts';
import { AdminBackButton } from '../components/admin'; import { AdminBackButton } from '../components/admin';
import { StatCard } from '../components/stats';
import { import {
ChartIcon, ChartIcon,
EmailIcon, EmailIcon,
TelegramSmallIcon, TelegramSmallIcon,
ArrowRightIcon, ArrowRightIcon,
GiftIcon, GiftIcon,
EyeIcon,
CheckCircleIcon,
BanknotesIcon,
PercentIcon,
TicketIcon,
CardIcon,
WalletIcon,
ChevronLeftIcon as ChevronLeftSmall, ChevronLeftIcon as ChevronLeftSmall,
ChevronRightIcon as ChevronRightSmall, ChevronRightIcon as ChevronRightSmall,
} from '@/components/icons'; } from '@/components/icons';
@@ -339,39 +347,62 @@ export default function AdminLandingStats() {
<div className="space-y-6"> <div className="space-y-6">
{/* Summary Cards */} {/* Summary Cards */}
<div className="grid grid-cols-2 gap-3 sm:grid-cols-4"> <div className="grid grid-cols-2 gap-3 sm:grid-cols-4">
<div className="rounded-xl border border-dark-700 bg-dark-800 p-4 text-center"> <StatCard
<div className="text-xl font-bold sm:text-2xl"> label={t('admin.landings.stats.created', 'Created')}
<span className="text-warning-400">{stats.total_created}</span> value={stats.total_created}
<span className="mx-1 text-dark-600">/</span> icon={<EyeIcon className="h-5 w-5" />}
<span className="text-success-400">{stats.total_successful}</span> tone="warning"
</div> />
<div className="text-xs text-dark-500"> <StatCard
{t('admin.landings.stats.created', 'Created')} /{' '} label={t('admin.landings.stats.paid', 'paid')}
{t('admin.landings.stats.paid', 'paid')} value={stats.total_successful}
</div> icon={<CheckCircleIcon className="h-5 w-5" />}
</div> tone="success"
<div className="rounded-xl border border-dark-700 bg-dark-800 p-4 text-center"> />
<div className="truncate text-xl font-bold text-accent-400 sm:text-2xl"> <StatCard
{formatWithCurrency(stats.total_revenue_kopeks / CHART_COMMON.KOPEKS_DIVISOR)} label={t('admin.landings.stats.revenue')}
</div> value={formatWithCurrency(stats.total_revenue_kopeks / CHART_COMMON.KOPEKS_DIVISOR)}
<div className="text-xs text-dark-500">{t('admin.landings.stats.revenue')}</div> icon={<BanknotesIcon className="h-5 w-5" />}
</div> tone="success"
<div className="rounded-xl border border-dark-700 bg-dark-800 p-4 text-center"> />
<div className="text-xl font-bold text-accent-400 sm:text-2xl">{stats.total_gifts}</div> <StatCard
<div className="text-xs text-dark-500">{t('admin.landings.stats.giftPurchases')}</div> label={t('admin.landings.stats.conversionRate')}
</div> value={`${stats.conversion_rate}%`}
<div className="rounded-xl border border-dark-700 bg-dark-800 p-4 text-center"> icon={<PercentIcon className="h-5 w-5" />}
<div className="text-xl font-bold text-dark-200 sm:text-2xl"> tone="accent"
{stats.conversion_rate}% />
</div> </div>
<div className="text-xs text-dark-500">{t('admin.landings.stats.conversionRate')}</div>
</div> {/* Breakdown Cards */}
<div className="grid grid-cols-2 gap-3 sm:grid-cols-4">
<StatCard
label={t('admin.landings.stats.purchases')}
value={stats.total_purchases}
icon={<TicketIcon className="h-5 w-5" />}
tone="accent"
/>
<StatCard
label={t('admin.landings.stats.regularPurchases')}
value={stats.total_regular}
icon={<CardIcon className="h-5 w-5" />}
/>
<StatCard
label={t('admin.landings.stats.giftPurchases')}
value={stats.total_gifts}
icon={<GiftIcon className="h-5 w-5" />}
tone="accent"
/>
<StatCard
label={t('admin.landings.stats.avgPurchase')}
value={formatWithCurrency(stats.avg_purchase_kopeks / CHART_COMMON.KOPEKS_DIVISOR)}
icon={<WalletIcon className="h-5 w-5" />}
/>
</div> </div>
{/* Charts */} {/* Charts */}
<div className="grid grid-cols-1 gap-4 lg:grid-cols-2"> <div className="grid grid-cols-1 gap-4 lg:grid-cols-2">
{/* Daily Purchases & Revenue */} {/* Daily Purchases & Revenue */}
<div className="rounded-xl border border-dark-700 bg-dark-800 p-4"> <div className="bento-card">
<h3 className="mb-4 font-medium text-dark-200"> <h3 className="mb-4 font-medium text-dark-200">
{t('admin.landings.stats.dailyChart')} {t('admin.landings.stats.dailyChart')}
</h3> </h3>
@@ -501,7 +532,7 @@ export default function AdminLandingStats() {
</div> </div>
{/* Daily Purchases Bar Chart */} {/* Daily Purchases Bar Chart */}
<div className="rounded-xl border border-dark-700 bg-dark-800 p-4"> <div className="bento-card">
<h3 className="mb-4 font-medium text-dark-200"> <h3 className="mb-4 font-medium text-dark-200">
{t('admin.landings.stats.dailyPurchases', 'Daily purchases')} {t('admin.landings.stats.dailyPurchases', 'Daily purchases')}
</h3> </h3>
@@ -557,7 +588,7 @@ export default function AdminLandingStats() {
</div> </div>
{/* Tariff Distribution -- full width */} {/* Tariff Distribution -- full width */}
<div className="rounded-xl border border-dark-700 bg-dark-800 p-4"> <div className="bento-card">
<h3 className="mb-4 font-medium text-dark-200"> <h3 className="mb-4 font-medium text-dark-200">
{t('admin.landings.stats.tariffChart')} {t('admin.landings.stats.tariffChart')}
</h3> </h3>
@@ -614,39 +645,9 @@ export default function AdminLandingStats() {
)} )}
</div> </div>
{/* Additional Stats Row */}
<div className="grid grid-cols-2 gap-3 sm:grid-cols-3">
<div className="rounded-xl border border-dark-700 bg-dark-800 p-4">
<div className="mb-1 text-sm text-dark-400">
{t('admin.landings.stats.avgPurchase')}
</div>
<div className="text-lg font-medium text-dark-200">
{formatWithCurrency(stats.avg_purchase_kopeks / CHART_COMMON.KOPEKS_DIVISOR)}
</div>
</div>
<div className="rounded-xl border border-dark-700 bg-dark-800 p-4">
<div className="mb-1 text-sm text-dark-400">
{t('admin.landings.stats.regularPurchases')}
</div>
<div className="text-lg font-medium text-dark-200">{stats.total_regular}</div>
</div>
<div className="col-span-2 rounded-xl border border-dark-700 bg-dark-800 p-4 sm:col-span-1">
<div className="mb-1 text-sm text-dark-400">{t('admin.landings.stats.funnel')}</div>
<div className="text-lg font-medium text-dark-200">
{stats.total_created}{' '}
<span className="text-sm text-dark-500">{t('admin.landings.stats.created')}</span>
{' / '}
{stats.total_successful}{' '}
<span className="text-sm text-dark-500">
{t('admin.landings.stats.paid', 'paid')}
</span>
</div>
</div>
</div>
{/* Gift vs Regular Donut */} {/* Gift vs Regular Donut */}
{stats.total_purchases > 0 && ( {stats.total_purchases > 0 && (
<div className="rounded-xl border border-dark-700 bg-dark-800 p-4"> <div className="bento-card">
<h3 className="mb-4 font-medium text-dark-200"> <h3 className="mb-4 font-medium text-dark-200">
{t('admin.landings.stats.giftBreakdown')} {t('admin.landings.stats.giftBreakdown')}
</h3> </h3>
@@ -706,7 +707,7 @@ export default function AdminLandingStats() {
)} )}
{/* Purchases List */} {/* Purchases List */}
<div className="rounded-xl border border-dark-700 bg-dark-800 p-4"> <div className="bento-card">
{/* Header row: title + status filter */} {/* Header row: title + status filter */}
<div className="mb-4 flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between"> <div className="mb-4 flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
<h3 className="font-medium text-dark-200">{t('admin.landings.purchases.title')}</h3> <h3 className="font-medium text-dark-200">{t('admin.landings.purchases.title')}</h3>