diff --git a/handlers/admin/admin_stats.py b/handlers/admin/admin_stats.py
new file mode 100644
index 00000000..113a6caa
--- /dev/null
+++ b/handlers/admin/admin_stats.py
@@ -0,0 +1,178 @@
+from datetime import datetime
+from io import BytesIO
+from typing import Any
+
+from aiogram import F, Router
+from aiogram.types import CallbackQuery, BufferedInputFile
+
+from filters.admin import IsAdminFilter
+from keyboards.admin.panel_kb import AdminPanelCallback, build_admin_back_kb
+from keyboards.admin.stats_kb import build_stats_kb
+from logger import logger
+
+router = Router()
+
+
+@router.callback_query(
+ AdminPanelCallback.filter(F.action == "stats"),
+ IsAdminFilter(),
+)
+async def user_stats_menu(callback_query: CallbackQuery, session: Any):
+ try:
+ total_users = await session.fetchval("SELECT COUNT(*) FROM connections")
+ total_keys = await session.fetchval("SELECT COUNT(*) FROM keys")
+ total_referrals = await session.fetchval("SELECT COUNT(*) FROM referrals")
+
+ total_payments_today = await session.fetchval(
+ "SELECT COALESCE(SUM(amount), 0) FROM payments WHERE created_at >= CURRENT_DATE"
+ )
+ total_payments_week = await session.fetchval(
+ "SELECT COALESCE(SUM(amount), 0) FROM payments WHERE created_at >= date_trunc('week', CURRENT_DATE)"
+ )
+ total_payments_month = await session.fetchval(
+ "SELECT COALESCE(SUM(amount), 0) FROM payments WHERE created_at >= date_trunc('month', CURRENT_DATE)"
+ )
+ total_payments_all_time = await session.fetchval(
+ "SELECT COALESCE(SUM(amount), 0) FROM payments"
+ )
+
+ active_keys = await session.fetchval(
+ "SELECT COUNT(*) FROM keys WHERE expiry_time > $1",
+ int(datetime.utcnow().timestamp() * 1000),
+ )
+ expired_keys = total_keys - active_keys
+
+ stats_message = (
+ f"📊 Подробная статистика проекта:\n\n"
+ f"👥 Пользователи:\n"
+ f" 🌐 Зарегистрировано: {total_users}\n"
+ f" 🤝 Привлеченных рефералов: {total_referrals}\n\n"
+ f"🔑 Ключи:\n"
+ f" 🌈 Всего сгенерировано: {total_keys}\n"
+ f" ✅ Действующих: {active_keys}\n"
+ f" ❌ Просроченных: {expired_keys}\n\n"
+ f"💰 Финансовая статистика:\n"
+ f" 📅 За день: {total_payments_today} ₽\n"
+ f" 📆 За неделю: {total_payments_week} ₽\n"
+ f" 📆 За месяц: {total_payments_month} ₽\n"
+ f" 🏦 За все время: {total_payments_all_time} ₽\n"
+ )
+
+ kb = build_stats_kb()
+
+ await callback_query.message.edit_text(
+ text=stats_message,
+ reply_markup=kb
+ )
+ except Exception as e:
+ logger.error(f"Error in user_stats_menu: {e}")
+
+
+@router.callback_query(
+ AdminPanelCallback.filter(F.action == "stats_export_users_csv"),
+ IsAdminFilter(),
+)
+async def export_users_csv(callback_query: CallbackQuery, session: Any):
+ kb = build_admin_back_kb("stats")
+
+ try:
+ users = await session.fetch(
+ """
+ SELECT
+ u.tg_id,
+ u.username,
+ u.first_name,
+ u.last_name,
+ u.language_code,
+ u.is_bot,
+ c.balance,
+ c.trial
+ FROM users u
+ LEFT JOIN connections c ON u.tg_id = c.tg_id
+ """
+ )
+
+ if not users:
+ await callback_query.message.answer(
+ text="📭 Нет пользователей для экспорта.",
+ reply_markup=kb
+ )
+ return
+
+ csv_data = "tg_id,username,first_name,last_name,language_code,is_bot,balance,trial\n" # Заголовки CSV
+ for user in users:
+ csv_data += f"{user['tg_id']},{user['username']},{user['first_name']},{user['last_name']},{user['language_code']},{user['is_bot']},{user['balance']},{user['trial']}\n"
+
+ file_name = BytesIO(csv_data.encode("utf-8-sig"))
+ file_name.seek(0)
+
+ file = BufferedInputFile(file_name.getvalue(), filename="users_export.csv")
+
+ await callback_query.message.answer_document(
+ file,
+ caption="📥 Экспорт пользователей в CSV",
+ reply_markup=kb
+ )
+ file_name.close()
+
+ except Exception as e:
+ logger.error(f"Ошибка при экспорте пользователей в CSV: {e}")
+ await callback_query.message.answer(
+ text="❗ Произошла ошибка при экспорте пользователей.",
+ reply_markup=kb
+ )
+
+
+@router.callback_query(
+ AdminPanelCallback.filter(F.action == "stats_export_payments_csv"),
+ IsAdminFilter(),
+)
+async def export_payments_csv(callback_query: CallbackQuery, session: Any):
+ kb = build_admin_back_kb("stats")
+
+ try:
+ payments = await session.fetch(
+ """
+ SELECT
+ u.tg_id,
+ u.username,
+ u.first_name,
+ u.last_name,
+ p.amount,
+ p.payment_system,
+ p.status,
+ p.created_at
+ FROM users u
+ JOIN payments p ON u.tg_id = p.tg_id
+ """
+ )
+
+ if not payments:
+ await callback_query.message.answer(
+ text="📭 Нет платежей для экспорта.",
+ reply_markup=kb
+ )
+ return
+
+ csv_data = "tg_id,username,first_name,last_name,amount,payment_system,status,created_at\n" # Заголовки CSV
+ for payment in payments:
+ csv_data += f"{payment['tg_id']},{payment['username']},{payment['first_name']},{payment['last_name']},{payment['amount']},{payment['payment_system']},{payment['status']},{payment['created_at']}\n"
+
+ file_name = BytesIO(csv_data.encode("utf-8-sig"))
+ file_name.seek(0)
+
+ file = BufferedInputFile(file_name.getvalue(), filename="payments_export.csv")
+
+ await callback_query.message.answer_document(
+ document=file,
+ caption="📥 Экспорт платежей в CSV",
+ reply_markup=kb
+ )
+ file_name.close()
+
+ except Exception as e:
+ logger.error(f"Ошибка при экспорте платежей в CSV: {e}")
+ await callback_query.message.answer(
+ text="❗ Произошла ошибка при экспорте платежей.",
+ reply_markup=kb
+ )
diff --git a/keyboards/admin/stats_kb.py b/keyboards/admin/stats_kb.py
new file mode 100644
index 00000000..3e3b9b04
--- /dev/null
+++ b/keyboards/admin/stats_kb.py
@@ -0,0 +1,24 @@
+from aiogram.types import InlineKeyboardMarkup
+from aiogram.utils.keyboard import InlineKeyboardBuilder
+
+from keyboards.admin.panel_kb import build_admin_back_btn, AdminPanelCallback
+
+
+def build_stats_kb() -> InlineKeyboardMarkup:
+ builder = InlineKeyboardBuilder()
+ builder.button(
+ text="🔄 Обновить",
+ callback_data=AdminPanelCallback(action="stats").pack()
+ )
+ builder.button(
+ text="📥 Выгрузить пользователей в CSV",
+ callback_data=AdminPanelCallback(action="stats_export_users_csv").pack()
+ )
+ builder.button(
+ text="📥 Выгрузить оплаты в CSV",
+ callback_data=AdminPanelCallback(action="stats_export_payments_csv").pack()
+ )
+ builder.row(
+ build_admin_back_btn("admin")
+ )
+ return builder.as_markup()