diff --git a/bot/handlers/admin/statistics.py b/bot/handlers/admin/statistics.py
index 25ef509..5210dbd 100644
--- a/bot/handlers/admin/statistics.py
+++ b/bot/handlers/admin/statistics.py
@@ -13,7 +13,7 @@ from bot.services.panel_api_service import PanelApiService
from bot.keyboards.inline.admin_keyboards import (
get_back_to_admin_panel_keyboard,
- get_back_to_user_management_keyboard,
+ get_user_ratings_keyboard,
)
from bot.middlewares.i18n import JsonI18n
@@ -39,6 +39,18 @@ def _format_rating_user_label(user_row: Dict[str, object]) -> str:
return " ".join(parts)
+def _format_rating_user_short_label(user_row: Dict[str, object]) -> str:
+ username = user_row.get("username")
+ first_name = user_row.get("first_name")
+ user_id = int(user_row.get("user_id", 0) or 0)
+
+ if username:
+ return f"@{html.escape(str(username))}"
+ if first_name:
+ return html.escape(str(first_name))
+ return f"ID {user_id}"
+
+
async def show_statistics_handler(callback: types.CallbackQuery,
i18n_data: dict, settings: Settings,
session: AsyncSession):
@@ -348,8 +360,22 @@ async def show_user_ratings_handler(
else:
text_parts.append(_("admin_user_ratings_empty"))
+ unique_users: Dict[int, Dict[str, object]] = {}
+ for row in traffic_top + invited_top + revenue_top:
+ user_id = int(row.get("user_id", 0) or 0)
+ if user_id <= 0 or user_id in unique_users:
+ continue
+ unique_users[user_id] = {
+ "user_id": user_id,
+ "label": _format_rating_user_short_label(row),
+ }
+
await callback.message.edit_text(
"\n".join(text_parts),
- reply_markup=get_back_to_user_management_keyboard(current_lang, i18n),
+ reply_markup=get_user_ratings_keyboard(
+ list(unique_users.values()),
+ current_lang,
+ i18n,
+ ),
parse_mode="HTML",
)
diff --git a/bot/keyboards/inline/admin_keyboards.py b/bot/keyboards/inline/admin_keyboards.py
index 66ad5ea..78f3512 100644
--- a/bot/keyboards/inline/admin_keyboards.py
+++ b/bot/keyboards/inline/admin_keyboards.py
@@ -480,3 +480,34 @@ def get_back_to_user_management_keyboard(lang: str,
builder.button(text=_(key="back_to_user_management_button"),
callback_data="admin_section:user_management")
return builder.as_markup()
+
+
+def get_user_ratings_keyboard(rating_users: List[dict],
+ lang: str,
+ i18n_instance) -> InlineKeyboardMarkup:
+ _ = lambda key, **kwargs: i18n_instance.gettext(lang, key, **kwargs)
+ builder = InlineKeyboardBuilder()
+
+ for entry in rating_users:
+ user_id = int(entry.get("user_id", 0) or 0)
+ if user_id <= 0:
+ continue
+
+ label = str(entry.get("label", "")).strip()
+ if label:
+ button_text = _("admin_user_ratings_open_user_button",
+ user_id=user_id,
+ user_label=label)
+ else:
+ button_text = _("admin_user_ratings_open_user_button_simple",
+ user_id=user_id)
+
+ builder.button(
+ text=button_text,
+ callback_data=f"admin_user_card_from_list:{user_id}:0",
+ )
+
+ builder.button(text=_(key="back_to_user_management_button"),
+ callback_data="admin_section:user_management")
+ builder.adjust(1)
+ return builder.as_markup()
diff --git a/locales/en.json b/locales/en.json
index f022b37..029318c 100644
--- a/locales/en.json
+++ b/locales/en.json
@@ -142,6 +142,8 @@
"admin_user_ratings_invited_item": "{rank}. {user} — {invited_count}",
"admin_user_ratings_revenue_item": "{rank}. {user} — {revenue} RUB",
"admin_user_ratings_empty": "No data yet.",
+ "admin_user_ratings_open_user_button": "👤 ID {user_id} ({user_label})",
+ "admin_user_ratings_open_user_button_simple": "👤 ID {user_id}",
"admin_payments_header": "💰 All Payments",
"admin_no_payments_found": "No payments found.",
"admin_export_payments_csv": "📊 Export CSV",
diff --git a/locales/ru.json b/locales/ru.json
index 96bd1c7..c69fee0 100644
--- a/locales/ru.json
+++ b/locales/ru.json
@@ -142,6 +142,8 @@
"admin_user_ratings_invited_item": "{rank}. {user} — {invited_count}",
"admin_user_ratings_revenue_item": "{rank}. {user} — {revenue} RUB",
"admin_user_ratings_empty": "Пока нет данных.",
+ "admin_user_ratings_open_user_button": "👤 ID {user_id} ({user_label})",
+ "admin_user_ratings_open_user_button_simple": "👤 ID {user_id}",
"admin_payments_header": "💰 Все платежи",
"admin_no_payments_found": "Платежи не найдены.",
"admin_export_payments_csv": "📊 Экспорт CSV",