Merge pull request #326 from BEDOLAGA-DEV/dev

Dev
This commit is contained in:
Egor
2026-03-18 23:34:24 +03:00
committed by GitHub
10 changed files with 99 additions and 15 deletions

View File

@@ -12,6 +12,7 @@ export interface SalesStatsParams {
export interface SalesSummary { export interface SalesSummary {
total_revenue_kopeks: number; total_revenue_kopeks: number;
manual_topup_kopeks: number;
active_subscriptions: number; active_subscriptions: number;
active_trials: number; active_trials: number;
new_trials: number; new_trials: number;

View File

@@ -15,4 +15,5 @@ export const METHOD_LABELS: Record<string, string> = {
kassa_ai: 'Kassa AI', kassa_ai: 'Kassa AI',
riopay: 'RioPay', riopay: 'RioPay',
severpay: 'SeverPay', severpay: 'SeverPay',
manual: 'Ручное пополнение',
}; };

View File

@@ -1166,7 +1166,8 @@
"newTrials": "New Trials", "newTrials": "New Trials",
"conversion": "Conversion", "conversion": "Conversion",
"renewals": "Renewals", "renewals": "Renewals",
"addonRevenue": "Add-on Revenue" "addonRevenue": "Add-on Revenue",
"manualTopup": "Manual Top-ups"
}, },
"trials": { "trials": {
"total": "Total Trials", "total": "Total Trials",

View File

@@ -1000,7 +1000,8 @@
"newTrials": "آزمایشی جدید", "newTrials": "آزمایشی جدید",
"conversion": "تبدیل", "conversion": "تبدیل",
"renewals": "تمدید", "renewals": "تمدید",
"addonRevenue": "درآمد خدمات اضافی" "addonRevenue": "درآمد خدمات اضافی",
"manualTopup": "شارژ دستی"
}, },
"trials": { "trials": {
"total": "کل آزمایشی", "total": "کل آزمایشی",

View File

@@ -1187,7 +1187,8 @@
"newTrials": "Новые триалы", "newTrials": "Новые триалы",
"conversion": "Конверсия", "conversion": "Конверсия",
"renewals": "Продления", "renewals": "Продления",
"addonRevenue": "Доп. услуги" "addonRevenue": "Доп. услуги",
"manualTopup": "Ручные пополнения"
}, },
"trials": { "trials": {
"total": "Всего триалов", "total": "Всего триалов",

View File

@@ -1000,7 +1000,8 @@
"newTrials": "新试用", "newTrials": "新试用",
"conversion": "转化率", "conversion": "转化率",
"renewals": "续订", "renewals": "续订",
"addonRevenue": "附加服务收入" "addonRevenue": "附加服务收入",
"manualTopup": "手动充值"
}, },
"trials": { "trials": {
"total": "试用总数", "total": "试用总数",

View File

@@ -478,17 +478,53 @@ export default function AdminPayments() {
</div> </div>
{/* User info */} {/* User info */}
{(payment.user_username || payment.user_telegram_id) && ( {(payment.user_username ||
payment.user_telegram_id ||
payment.user_email) && (
<div className="mt-2 text-sm text-dark-400"> <div className="mt-2 text-sm text-dark-400">
<span className="text-dark-500">{t('admin.payments.user')}:</span>{' '} <span className="text-dark-500">{t('admin.payments.user')}:</span>{' '}
{payment.user_username && ( {payment.user_id ? (
<span className="text-dark-200">@{payment.user_username}</span> <button
)} onClick={(e) => {
{payment.user_username && payment.user_telegram_id && ( e.stopPropagation();
<span className="text-dark-500"> &middot; </span> navigate(`/admin/users/${payment.user_id}`);
)} }}
{payment.user_telegram_id && ( className="inline-flex items-center gap-1 transition-colors hover:underline"
<span className="text-dark-300">TG: {payment.user_telegram_id}</span> >
{payment.user_username && (
<span className="text-accent-400">@{payment.user_username}</span>
)}
{payment.user_username &&
(payment.user_telegram_id || payment.user_email) && (
<span className="text-dark-500"> &middot; </span>
)}
{payment.user_telegram_id && (
<span className="text-accent-300">
TG: {payment.user_telegram_id}
</span>
)}
{!payment.user_telegram_id && payment.user_email && (
<span className="text-accent-300">{payment.user_email}</span>
)}
</button>
) : (
<>
{payment.user_username && (
<span className="text-dark-200">@{payment.user_username}</span>
)}
{payment.user_username &&
(payment.user_telegram_id || payment.user_email) && (
<span className="text-dark-500"> &middot; </span>
)}
{payment.user_telegram_id && (
<span className="text-dark-300">
TG: {payment.user_telegram_id}
</span>
)}
{!payment.user_telegram_id && payment.user_email && (
<span className="text-dark-300">{payment.user_email}</span>
)}
</>
)} )}
</div> </div>
)} )}

View File

@@ -82,7 +82,7 @@ export default function AdminSalesStats() {
{t('admin.salesStats.loadError')} {t('admin.salesStats.loadError')}
</div> </div>
)} )}
<div className="grid grid-cols-2 gap-3 sm:grid-cols-3 lg:grid-cols-4 xl:grid-cols-7"> <div className="grid grid-cols-2 gap-3 sm:grid-cols-3 lg:grid-cols-4 xl:grid-cols-8">
<StatCard <StatCard
label={t('admin.salesStats.summary.revenue')} label={t('admin.salesStats.summary.revenue')}
value={ value={
@@ -129,6 +129,15 @@ export default function AdminSalesStats() {
} }
valueClassName="text-accent-400" valueClassName="text-accent-400"
/> />
<StatCard
label={t('admin.salesStats.summary.manualTopup')}
value={
summaryLoading
? '...'
: formatWithCurrency((summary?.manual_topup_kopeks ?? 0) / SALES_STATS.KOPEKS_DIVISOR)
}
valueClassName="text-warning-400"
/>
</div> </div>
{/* Tabs */} {/* Tabs */}

View File

@@ -15,6 +15,7 @@ import SubscriptionCardExpired from '../components/dashboard/SubscriptionCardExp
import TrialOfferCard from '../components/dashboard/TrialOfferCard'; import TrialOfferCard from '../components/dashboard/TrialOfferCard';
import StatsGrid from '../components/dashboard/StatsGrid'; import StatsGrid from '../components/dashboard/StatsGrid';
import { giftApi } from '../api/gift'; import { giftApi } from '../api/gift';
import { promoApi } from '../api/promo';
import PendingGiftCard from '../components/dashboard/PendingGiftCard'; import PendingGiftCard from '../components/dashboard/PendingGiftCard';
import { API } from '../config/constants'; import { API } from '../config/constants';
@@ -89,6 +90,13 @@ export default function Dashboard() {
retry: false, retry: false,
}); });
const { data: promoGroupData } = useQuery({
queryKey: ['promo-group-discounts'],
queryFn: promoApi.getGroupDiscounts,
staleTime: 60_000,
retry: false,
});
const activateTrialMutation = useMutation({ const activateTrialMutation = useMutation({
mutationFn: subscriptionApi.activateTrial, mutationFn: subscriptionApi.activateTrial,
onSuccess: () => { onSuccess: () => {
@@ -227,7 +235,31 @@ export default function Dashboard() {
<h1 className="text-2xl font-bold text-dark-50 sm:text-3xl"> <h1 className="text-2xl font-bold text-dark-50 sm:text-3xl">
{t('dashboard.welcome', { name: user?.first_name || user?.username || '' })} {t('dashboard.welcome', { name: user?.first_name || user?.username || '' })}
</h1> </h1>
<p className="mt-1 text-dark-400">{t('dashboard.yourSubscription')}</p> <div className="mt-1 flex flex-wrap items-center gap-2">
<p className="text-dark-400">{t('dashboard.yourSubscription')}</p>
{promoGroupData?.group_name && (
<span
className="inline-flex max-w-[160px] items-center gap-1 rounded-full px-2.5 py-0.5 text-[11px] font-semibold"
style={{
background: 'rgba(var(--color-accent-400), 0.1)',
border: '1px solid rgba(var(--color-accent-400), 0.2)',
color: 'rgb(var(--color-accent-400))',
}}
>
<svg
className="shrink-0"
width="10"
height="10"
viewBox="0 0 24 24"
fill="currentColor"
aria-hidden="true"
>
<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z" />
</svg>
<span className="truncate">{promoGroupData.group_name}</span>
</span>
)}
</div>
</div> </div>
{/* Pending Gift Activations */} {/* Pending Gift Activations */}

View File

@@ -567,6 +567,7 @@ export interface PendingPayment {
user_id?: number; user_id?: number;
user_telegram_id?: number; user_telegram_id?: number;
user_username?: string | null; user_username?: string | null;
user_email?: string | null;
} }
export interface ManualCheckResponse { export interface ManualCheckResponse {