mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-28 09:33:46 +00:00
@@ -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;
|
||||||
|
|||||||
@@ -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: 'Ручное пополнение',
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -1000,7 +1000,8 @@
|
|||||||
"newTrials": "آزمایشی جدید",
|
"newTrials": "آزمایشی جدید",
|
||||||
"conversion": "تبدیل",
|
"conversion": "تبدیل",
|
||||||
"renewals": "تمدید",
|
"renewals": "تمدید",
|
||||||
"addonRevenue": "درآمد خدمات اضافی"
|
"addonRevenue": "درآمد خدمات اضافی",
|
||||||
|
"manualTopup": "شارژ دستی"
|
||||||
},
|
},
|
||||||
"trials": {
|
"trials": {
|
||||||
"total": "کل آزمایشی",
|
"total": "کل آزمایشی",
|
||||||
|
|||||||
@@ -1187,7 +1187,8 @@
|
|||||||
"newTrials": "Новые триалы",
|
"newTrials": "Новые триалы",
|
||||||
"conversion": "Конверсия",
|
"conversion": "Конверсия",
|
||||||
"renewals": "Продления",
|
"renewals": "Продления",
|
||||||
"addonRevenue": "Доп. услуги"
|
"addonRevenue": "Доп. услуги",
|
||||||
|
"manualTopup": "Ручные пополнения"
|
||||||
},
|
},
|
||||||
"trials": {
|
"trials": {
|
||||||
"total": "Всего триалов",
|
"total": "Всего триалов",
|
||||||
|
|||||||
@@ -1000,7 +1000,8 @@
|
|||||||
"newTrials": "新试用",
|
"newTrials": "新试用",
|
||||||
"conversion": "转化率",
|
"conversion": "转化率",
|
||||||
"renewals": "续订",
|
"renewals": "续订",
|
||||||
"addonRevenue": "附加服务收入"
|
"addonRevenue": "附加服务收入",
|
||||||
|
"manualTopup": "手动充值"
|
||||||
},
|
},
|
||||||
"trials": {
|
"trials": {
|
||||||
"total": "试用总数",
|
"total": "试用总数",
|
||||||
|
|||||||
@@ -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_id ? (
|
||||||
|
<button
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
navigate(`/admin/users/${payment.user_id}`);
|
||||||
|
}}
|
||||||
|
className="inline-flex items-center gap-1 transition-colors hover:underline"
|
||||||
|
>
|
||||||
{payment.user_username && (
|
{payment.user_username && (
|
||||||
<span className="text-dark-200">@{payment.user_username}</span>
|
<span className="text-accent-400">@{payment.user_username}</span>
|
||||||
)}
|
)}
|
||||||
{payment.user_username && payment.user_telegram_id && (
|
{payment.user_username &&
|
||||||
|
(payment.user_telegram_id || payment.user_email) && (
|
||||||
<span className="text-dark-500"> · </span>
|
<span className="text-dark-500"> · </span>
|
||||||
)}
|
)}
|
||||||
{payment.user_telegram_id && (
|
{payment.user_telegram_id && (
|
||||||
<span className="text-dark-300">TG: {payment.user_telegram_id}</span>
|
<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"> · </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>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -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 */}
|
||||||
|
|||||||
@@ -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 */}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user