From bd12113294c92379fc7fee6d4c25f1d73e3de856 Mon Sep 17 00:00:00 2001 From: c0mrade Date: Tue, 2 Jun 2026 16:53:30 +0300 Subject: [PATCH] =?UTF-8?q?feat(sales-stats):=20=D0=BA=D0=B0=D1=80=D1=82?= =?UTF-8?q?=D0=BE=D1=87=D0=BA=D0=B8=20=D0=B2=D0=BE=D0=B7=D0=B2=D1=80=D0=B0?= =?UTF-8?q?=D1=82=D0=BE=D0=B2=20=D0=B2=20=D1=81=D0=B2=D0=BE=D0=B4=D0=BA?= =?UTF-8?q?=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Две карточки — количество и сумма возвратов за период (красный тон). Сетку сводки перевёл на 2/5 (10 карточек делятся ровно, без пустых ячеек). StatCard получил invertDelta: для «меньше — лучше» метрик рост красится в красный, падение в зелёный (стрелка по факту). 4 локали. --- src/api/adminSalesStats.ts | 2 ++ src/components/stats/StatCard.tsx | 13 ++++++++++++- src/locales/en.json | 4 +++- src/locales/fa.json | 4 +++- src/locales/ru.json | 4 +++- src/locales/zh.json | 4 +++- src/pages/AdminSalesStats.tsx | 22 +++++++++++++++++++++- 7 files changed, 47 insertions(+), 6 deletions(-) diff --git a/src/api/adminSalesStats.ts b/src/api/adminSalesStats.ts index 7b5f261..8faabc8 100644 --- a/src/api/adminSalesStats.ts +++ b/src/api/adminSalesStats.ts @@ -19,6 +19,8 @@ export interface SalesSummary { trial_to_paid_conversion: number; renewals_count: number; addon_revenue_kopeks: number; + refunds_count: number; + refunds_kopeks: number; } // ============ Trials ============ diff --git a/src/components/stats/StatCard.tsx b/src/components/stats/StatCard.tsx index 79f2600..92189ae 100644 --- a/src/components/stats/StatCard.tsx +++ b/src/components/stats/StatCard.tsx @@ -26,6 +26,9 @@ interface StatCardProps { valueClassName?: string; /** Optional period-over-period change shown under the value. */ delta?: StatCardDelta | null; + /** For "lower is better" metrics (e.g. refunds): keep the real arrow but flip + * the colour, so a rise reads as bad (red) and a drop as good (green). */ + invertDelta?: boolean; } export function StatCard({ @@ -35,10 +38,18 @@ export function StatCard({ tone = 'neutral', valueClassName, delta, + invertDelta = false, }: StatCardProps) { const toneStyle = TONE[tone]; const valueClass = valueClassName ?? toneStyle.value; - const trendStyle = delta ? (TREND_STYLES[delta.trend] ?? TREND_STYLES.stable) : null; + const baseTrend = delta ? (TREND_STYLES[delta.trend] ?? TREND_STYLES.stable) : null; + const trendStyle = + baseTrend && invertDelta && delta && delta.trend !== 'stable' + ? { + arrow: baseTrend.arrow, + className: TREND_STYLES[delta.trend === 'up' ? 'down' : 'up'].className, + } + : baseTrend; return (
diff --git a/src/locales/en.json b/src/locales/en.json index 6a482c4..68bfaac 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -1306,7 +1306,9 @@ "conversion": "Conversion", "renewals": "Renewals", "addonRevenue": "Add-on Revenue", - "manualTopup": "Manual Top-ups" + "manualTopup": "Manual Top-ups", + "refundsCount": "Refunds", + "refundsAmount": "Refunded amount" }, "trials": { "total": "Total Trials", diff --git a/src/locales/fa.json b/src/locales/fa.json index 26cde24..2c744e2 100644 --- a/src/locales/fa.json +++ b/src/locales/fa.json @@ -1179,7 +1179,9 @@ "conversion": "تبدیل", "renewals": "تمدید", "addonRevenue": "درآمد خدمات اضافی", - "manualTopup": "شارژ دستی" + "manualTopup": "شارژ دستی", + "refundsCount": "بازپرداخت‌ها", + "refundsAmount": "مبلغ بازپرداخت" }, "trials": { "total": "کل آزمایشی", diff --git a/src/locales/ru.json b/src/locales/ru.json index 872bd7c..b77dbec 100644 --- a/src/locales/ru.json +++ b/src/locales/ru.json @@ -1328,7 +1328,9 @@ "conversion": "Конверсия", "renewals": "Продления", "addonRevenue": "Доп. услуги", - "manualTopup": "Ручные пополнения" + "manualTopup": "Ручные пополнения", + "refundsCount": "Возвраты", + "refundsAmount": "Сумма возвратов" }, "trials": { "total": "Всего триалов", diff --git a/src/locales/zh.json b/src/locales/zh.json index dca06ba..6a9d6a1 100644 --- a/src/locales/zh.json +++ b/src/locales/zh.json @@ -1179,7 +1179,9 @@ "conversion": "转化率", "renewals": "续订", "addonRevenue": "附加服务收入", - "manualTopup": "手动充值" + "manualTopup": "手动充值", + "refundsCount": "退款", + "refundsAmount": "退款金额" }, "trials": { "total": "试用总数", diff --git a/src/pages/AdminSalesStats.tsx b/src/pages/AdminSalesStats.tsx index 55cdb8d..bd044db 100644 --- a/src/pages/AdminSalesStats.tsx +++ b/src/pages/AdminSalesStats.tsx @@ -13,6 +13,7 @@ import { GiftIcon, PercentIcon, RepeatIcon, + ResetIcon, RocketIcon, SparklesIcon, TicketIcon, @@ -115,6 +116,7 @@ export default function AdminSalesStats() { renewals: computeDelta(summary.renewals_count, prevSummary.renewals_count), addonRevenue: computeDelta(summary.addon_revenue_kopeks, prevSummary.addon_revenue_kopeks), manualTopup: computeDelta(summary.manual_topup_kopeks, prevSummary.manual_topup_kopeks), + refunds: computeDelta(summary.refunds_kopeks, prevSummary.refunds_kopeks), }; }, [summary, prevSummary]); @@ -148,7 +150,7 @@ export default function AdminSalesStats() { {t('admin.salesStats.loadError')}
)} -
+
+ } + tone="error" + /> + } + tone="error" + delta={deltas?.refunds} + invertDelta + />
{/* Tabs */}