diff --git a/bot/handlers/admin/promo_codes.py b/bot/handlers/admin/promo_codes.py
index a7ffceb..623624c 100644
--- a/bot/handlers/admin/promo_codes.py
+++ b/bot/handlers/admin/promo_codes.py
@@ -1016,6 +1016,110 @@ async def process_promo_edit_validity_handler(message: types.Message, state: FSM
))
+@router.callback_query(F.data.startswith("promo_toggle:"))
+async def promo_toggle_handler(callback: types.CallbackQuery, i18n_data: dict,
+ settings: Settings, session: AsyncSession):
+ """Toggle promo code active status"""
+ current_lang = i18n_data.get("current_language", settings.DEFAULT_LANGUAGE)
+ i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance")
+ if not i18n or not callback.message:
+ await callback.answer("Error", show_alert=True)
+ return
+ _ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs)
+
+ promo_id = int(callback.data.split(":")[1])
+ promo = await promo_code_dal.get_promo_code_by_id(session, promo_id)
+ if not promo:
+ await callback.answer(_("admin_promo_not_found", default="Промокод не найден"), show_alert=True)
+ return
+
+ # Toggle status
+ new_status = not promo.is_active
+ update_data = {"is_active": new_status}
+
+ updated = await promo_code_dal.update_promo_code(session, promo_id, update_data)
+ if updated:
+ await session.commit()
+
+ status_text = _("admin_promo_status_activated", default="активирован") if new_status else _("admin_promo_status_deactivated", default="деактивирован")
+ success_text = _(
+ "admin_promo_toggle_success",
+ default="✅ Промокод {code} {status}",
+ code=promo.code,
+ status=status_text
+ )
+
+ await callback.answer(success_text, show_alert=True)
+
+ # Refresh the detail view
+ await promo_detail_handler(callback, i18n_data, settings, session)
+ else:
+ await session.rollback()
+ await callback.answer(_(
+ "admin_promo_toggle_failed",
+ default="❌ Ошибка изменения статуса промокода"
+ ), show_alert=True)
+
+
+@router.callback_query(F.data.startswith("promo_activations:"))
+async def promo_activations_handler(callback: types.CallbackQuery, i18n_data: dict,
+ settings: Settings, session: AsyncSession):
+ """Show promo code activations"""
+ current_lang = i18n_data.get("current_language", settings.DEFAULT_LANGUAGE)
+ i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance")
+ if not i18n or not callback.message:
+ await callback.answer("Error", show_alert=True)
+ return
+ _ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs)
+
+ promo_id = int(callback.data.split(":")[1])
+ promo = await promo_code_dal.get_promo_code_by_id(session, promo_id)
+ if not promo:
+ await callback.answer(_("admin_promo_not_found", default="Промокод не найден"), show_alert=True)
+ return
+
+ # Get activations for this promo code
+ activations = await promo_code_dal.get_promo_activations_by_code_id(session, promo_id)
+
+ if not activations:
+ text = _(
+ "admin_promo_no_activations",
+ default="📋 Активации промокода: {code}\n\n❌ Активаций не найдено",
+ code=promo.code
+ )
+ else:
+ text_parts = [_(
+ "admin_promo_activations_header",
+ default="📋 Активации промокода: {code}\n\n",
+ code=promo.code
+ )]
+
+ for activation in activations:
+ activation_date = activation.created_at.strftime('%Y-%m-%d %H:%M') if activation.created_at else "N/A"
+ text_parts.append(_(
+ "admin_promo_activation_item",
+ default="👤 User ID: {user_id}\n📅 Дата: {date}\n",
+ user_id=activation.user_id,
+ date=activation_date
+ ))
+
+ text = "".join(text_parts)
+
+ # Build keyboard with back button
+ kb = InlineKeyboardBuilder()
+ kb.row(
+ InlineKeyboardButton(
+ text=_("admin_promo_back_to_detail_button", default="⬅️ К промокоду"),
+ callback_data=f"promo_detail:{promo_id}")
+ )
+
+ await callback.message.edit_text(
+ text,
+ reply_markup=kb.as_markup(),
+ parse_mode="HTML")
+ await callback.answer()
+
+
@router.callback_query(F.data.startswith("promo_delete:"))
async def promo_delete_handler(callback: types.CallbackQuery, i18n_data: dict,
settings: Settings, session: AsyncSession):
diff --git a/locales/en.json b/locales/en.json
index 4729f8a..ef6d71b 100644
--- a/locales/en.json
+++ b/locales/en.json
@@ -413,11 +413,19 @@
"admin_promo_card_created_by": "👤 Created by: {creator}",
"admin_promo_status_active": "✅ Active",
"admin_promo_status_inactive": "🚫 Inactive",
+ "admin_promo_status_activated": "activated",
+ "admin_promo_status_deactivated": "deactivated",
"admin_promo_status_expired": "⏰ Expired",
"admin_promo_status_used_up": "🔄 Used up",
"admin_promo_toggle_status_button": "🔄 On/Off",
"admin_promo_view_activations_button": "📋 Activations",
"admin_promo_back_to_list_button": "⬅️ Back to list",
+ "admin_promo_toggle_success": "✅ Promo code {code} {status}",
+ "admin_promo_toggle_failed": "❌ Error changing promo code status",
+ "admin_promo_no_activations": "📋 Activations of promo code: {code}\n\n❌ No activations found",
+ "admin_promo_activations_header": "📋 Activations of promo code: {code}\n\n",
+ "admin_promo_activation_item": "👤 User ID: {user_id}\n📅 Date: {date}\n",
+ "admin_promo_back_to_detail_button": "⬅️ Back to promo",
"admin_panel_online_label": "Online",
"admin_panel_active_label": "Active",
diff --git a/locales/ru.json b/locales/ru.json
index 2d1c7dd..086d864 100644
--- a/locales/ru.json
+++ b/locales/ru.json
@@ -412,11 +412,19 @@
"admin_promo_card_created_by": "👤 Создал: {creator}",
"admin_promo_status_active": "✅ Активен",
"admin_promo_status_inactive": "🚫 Неактивен",
+ "admin_promo_status_activated": "активирован",
+ "admin_promo_status_deactivated": "деактивирован",
"admin_promo_status_expired": "⏰ Истек",
"admin_promo_status_used_up": "🔄 Исчерпан",
"admin_promo_toggle_status_button": "🔄 Вкл/Выкл",
"admin_promo_view_activations_button": "📋 Активации",
"admin_promo_back_to_list_button": "⬅️ К списку",
+ "admin_promo_toggle_success": "✅ Промокод {code} {status}",
+ "admin_promo_toggle_failed": "❌ Ошибка изменения статуса промокода",
+ "admin_promo_no_activations": "📋 Активации промокода: {code}\n\n❌ Активаций не найдено",
+ "admin_promo_activations_header": "📋 Активации промокода: {code}\n\n",
+ "admin_promo_activation_item": "👤 User ID: {user_id}\n📅 Дата: {date}\n",
+ "admin_promo_back_to_detail_button": "⬅️ К промокоду",
"admin_panel_online_label": "Онлайн",
"admin_panel_active_label": "Активных",