439 lines
13 KiB
Python
439 lines
13 KiB
Python
"""Handlers for managing reminders (view, edit, delete)."""
|
||
|
||
from aiogram import Router, F
|
||
from aiogram.types import Message, CallbackQuery
|
||
from aiogram.fsm.context import FSMContext
|
||
from sqlalchemy.ext.asyncio import AsyncSession
|
||
|
||
from bot.states.reminder_states import EditReminderStates
|
||
from bot.keyboards.pagination import get_reminders_list_keyboard, PaginationCallback
|
||
from bot.keyboards.reminders import (
|
||
get_reminder_details_keyboard,
|
||
get_edit_menu_keyboard,
|
||
get_interval_selection_keyboard,
|
||
get_confirmation_keyboard,
|
||
ReminderActionCallback,
|
||
ReminderEditCallback,
|
||
ReminderIntervalCallback,
|
||
ConfirmCallback,
|
||
)
|
||
from bot.keyboards.main_menu import get_main_menu_keyboard
|
||
from bot.services.user_service import UserService
|
||
from bot.services.reminders_service import RemindersService
|
||
from bot.utils.formatting import format_datetime, format_interval_days
|
||
from bot.utils.validators import validate_time_format, validate_days_interval
|
||
from bot.logging_config import get_logger
|
||
|
||
logger = get_logger(__name__)
|
||
|
||
router = Router(name="reminders_manage")
|
||
reminders_service = RemindersService()
|
||
|
||
|
||
@router.message(F.text == "📋 Мои напоминания")
|
||
async def show_reminders_list(
|
||
message: Message,
|
||
session: AsyncSession,
|
||
) -> None:
|
||
"""
|
||
Show user's reminders list.
|
||
|
||
Args:
|
||
message: Telegram message
|
||
session: Database session
|
||
"""
|
||
user = await UserService.ensure_user_exists(session, message.from_user)
|
||
reminders = await reminders_service.get_user_all_reminders(session, user.id)
|
||
|
||
if not reminders:
|
||
await message.answer(
|
||
"У тебя пока нет напоминаний.\n\n"
|
||
"Нажми «➕ Новое напоминание», чтобы создать первое!",
|
||
reply_markup=get_main_menu_keyboard(),
|
||
)
|
||
return
|
||
|
||
await message.answer(
|
||
f"📋 Твои напоминания ({len(reminders)}):",
|
||
reply_markup=get_reminders_list_keyboard(reminders, page=0),
|
||
)
|
||
|
||
|
||
@router.callback_query(PaginationCallback.filter(F.action.in_(["prev", "next"])))
|
||
async def paginate_reminders(
|
||
callback: CallbackQuery,
|
||
callback_data: PaginationCallback,
|
||
session: AsyncSession,
|
||
) -> None:
|
||
"""
|
||
Handle pagination for reminders list.
|
||
|
||
Args:
|
||
callback: Callback query
|
||
callback_data: Parsed callback data
|
||
session: Database session
|
||
"""
|
||
user = await UserService.ensure_user_exists(session, callback.from_user)
|
||
reminders = await reminders_service.get_user_all_reminders(session, user.id)
|
||
|
||
await callback.message.edit_reply_markup(
|
||
reply_markup=get_reminders_list_keyboard(reminders, page=callback_data.page)
|
||
)
|
||
await callback.answer()
|
||
|
||
|
||
@router.callback_query(PaginationCallback.filter(F.action == "select"))
|
||
async def show_reminder_details(
|
||
callback: CallbackQuery,
|
||
callback_data: PaginationCallback,
|
||
session: AsyncSession,
|
||
) -> None:
|
||
"""
|
||
Show reminder details.
|
||
|
||
Args:
|
||
callback: Callback query
|
||
callback_data: Parsed callback data
|
||
session: Database session
|
||
"""
|
||
reminder = await reminders_service.get_reminder(session, callback_data.reminder_id)
|
||
|
||
if not reminder:
|
||
await callback.answer("Напоминание не найдено", show_alert=True)
|
||
return
|
||
|
||
status = "Активно ✅" if reminder.is_active else "На паузе ⏸"
|
||
last_done = (
|
||
format_datetime(reminder.last_done_at)
|
||
if reminder.last_done_at
|
||
else "Ещё не выполнялось"
|
||
)
|
||
|
||
details_text = (
|
||
f"📝 <b>Напоминание #{reminder.id}</b>\n\n"
|
||
f"<b>Текст:</b> {reminder.text}\n\n"
|
||
f"<b>Периодичность:</b> {format_interval_days(reminder.days_interval)}\n"
|
||
f"<b>Время:</b> {reminder.time_of_day.strftime('%H:%M')}\n"
|
||
f"<b>Статус:</b> {status}\n\n"
|
||
f"<b>Следующее напоминание:</b> {format_datetime(reminder.next_run_at)}\n"
|
||
f"<b>Последнее выполнение:</b> {last_done}\n"
|
||
f"<b>Выполнено раз:</b> {reminder.total_done_count}"
|
||
)
|
||
|
||
await callback.message.edit_text(
|
||
details_text,
|
||
reply_markup=get_reminder_details_keyboard(reminder.id, reminder.is_active),
|
||
parse_mode="HTML",
|
||
)
|
||
await callback.answer()
|
||
|
||
|
||
# ==================== Edit Reminder Flow ====================
|
||
|
||
|
||
@router.callback_query(ReminderActionCallback.filter(F.action == "edit"))
|
||
async def start_edit_reminder(
|
||
callback: CallbackQuery,
|
||
callback_data: ReminderActionCallback,
|
||
state: FSMContext,
|
||
) -> None:
|
||
"""
|
||
Start editing reminder.
|
||
|
||
Args:
|
||
callback: Callback query
|
||
callback_data: Parsed callback data
|
||
state: FSM state context
|
||
"""
|
||
await state.update_data(reminder_id=callback_data.reminder_id)
|
||
await state.set_state(EditReminderStates.selecting_field)
|
||
|
||
await callback.message.edit_text(
|
||
"Что изменить?",
|
||
reply_markup=get_edit_menu_keyboard(callback_data.reminder_id),
|
||
)
|
||
await callback.answer()
|
||
|
||
|
||
@router.callback_query(
|
||
EditReminderStates.selecting_field,
|
||
ReminderEditCallback.filter(F.action == "back")
|
||
)
|
||
async def cancel_edit(
|
||
callback: CallbackQuery,
|
||
callback_data: ReminderEditCallback,
|
||
state: FSMContext,
|
||
session: AsyncSession,
|
||
) -> None:
|
||
"""
|
||
Cancel editing and return to reminder details.
|
||
|
||
Args:
|
||
callback: Callback query
|
||
callback_data: Parsed callback data
|
||
state: FSM state context
|
||
session: Database session
|
||
"""
|
||
await state.clear()
|
||
|
||
# Show reminder details again
|
||
reminder = await reminders_service.get_reminder(session, callback_data.reminder_id)
|
||
if not reminder:
|
||
await callback.answer("Напоминание не найдено", show_alert=True)
|
||
return
|
||
|
||
status = "Активно ✅" if reminder.is_active else "На паузе ⏸"
|
||
last_done = (
|
||
format_datetime(reminder.last_done_at)
|
||
if reminder.last_done_at
|
||
else "Ещё не выполнялось"
|
||
)
|
||
|
||
details_text = (
|
||
f"📝 <b>Напоминание #{reminder.id}</b>\n\n"
|
||
f"<b>Текст:</b> {reminder.text}\n\n"
|
||
f"<b>Периодичность:</b> {format_interval_days(reminder.days_interval)}\n"
|
||
f"<b>Время:</b> {reminder.time_of_day.strftime('%H:%M')}\n"
|
||
f"<b>Статус:</b> {status}\n\n"
|
||
f"<b>Следующее напоминание:</b> {format_datetime(reminder.next_run_at)}\n"
|
||
f"<b>Последнее выполнение:</b> {last_done}"
|
||
)
|
||
|
||
await callback.message.edit_text(
|
||
details_text,
|
||
reply_markup=get_reminder_details_keyboard(reminder.id, reminder.is_active),
|
||
parse_mode="HTML",
|
||
)
|
||
await callback.answer()
|
||
|
||
|
||
@router.callback_query(
|
||
EditReminderStates.selecting_field,
|
||
ReminderEditCallback.filter(F.action == "text")
|
||
)
|
||
async def edit_text_start(
|
||
callback: CallbackQuery,
|
||
callback_data: ReminderEditCallback,
|
||
state: FSMContext,
|
||
) -> None:
|
||
"""
|
||
Start editing reminder text.
|
||
|
||
Args:
|
||
callback: Callback query
|
||
callback_data: Parsed callback data
|
||
state: FSM state context
|
||
"""
|
||
await state.set_state(EditReminderStates.editing_text)
|
||
await callback.message.edit_text("Введи новый текст напоминания:")
|
||
await callback.answer()
|
||
|
||
|
||
@router.message(EditReminderStates.editing_text)
|
||
async def edit_text_process(
|
||
message: Message,
|
||
state: FSMContext,
|
||
session: AsyncSession,
|
||
) -> None:
|
||
"""
|
||
Process new reminder text.
|
||
|
||
Args:
|
||
message: Telegram message
|
||
state: FSM state context
|
||
session: Database session
|
||
"""
|
||
text = message.text.strip()
|
||
|
||
if not text or len(text) > 1000:
|
||
await message.answer("Текст должен быть от 1 до 1000 символов. Попробуй ещё раз:")
|
||
return
|
||
|
||
data = await state.get_data()
|
||
reminder_id = data["reminder_id"]
|
||
|
||
reminder = await reminders_service.update_reminder_text(session, reminder_id, text)
|
||
|
||
if not reminder:
|
||
await message.answer("Напоминание не найдено")
|
||
await state.clear()
|
||
return
|
||
|
||
await state.clear()
|
||
await message.answer(
|
||
f"✅ Текст обновлён!\n\nНовый текст: {text}",
|
||
reply_markup=get_main_menu_keyboard(),
|
||
)
|
||
|
||
|
||
@router.callback_query(
|
||
EditReminderStates.selecting_field,
|
||
ReminderEditCallback.filter(F.action == "interval")
|
||
)
|
||
async def edit_interval_start(
|
||
callback: CallbackQuery,
|
||
callback_data: ReminderEditCallback,
|
||
state: FSMContext,
|
||
) -> None:
|
||
"""
|
||
Start editing reminder interval.
|
||
|
||
Args:
|
||
callback: Callback query
|
||
callback_data: Parsed callback data
|
||
state: FSM state context
|
||
"""
|
||
await state.set_state(EditReminderStates.editing_interval)
|
||
await callback.message.edit_text(
|
||
"Выбери новый период или введи количество дней:",
|
||
reply_markup=get_interval_selection_keyboard(),
|
||
)
|
||
await callback.answer()
|
||
|
||
|
||
@router.callback_query(
|
||
EditReminderStates.editing_interval,
|
||
ReminderIntervalCallback.filter()
|
||
)
|
||
async def edit_interval_button(
|
||
callback: CallbackQuery,
|
||
callback_data: ReminderIntervalCallback,
|
||
state: FSMContext,
|
||
session: AsyncSession,
|
||
) -> None:
|
||
"""
|
||
Process interval selection via button.
|
||
|
||
Args:
|
||
callback: Callback query
|
||
callback_data: Parsed callback data
|
||
state: FSM state context
|
||
session: Database session
|
||
"""
|
||
if callback_data.days == 0:
|
||
await callback.message.edit_text("Введи количество дней (целое положительное число):")
|
||
await callback.answer()
|
||
return
|
||
|
||
data = await state.get_data()
|
||
reminder_id = data["reminder_id"]
|
||
|
||
reminder = await reminders_service.update_reminder_interval(
|
||
session, reminder_id, callback_data.days
|
||
)
|
||
|
||
if not reminder:
|
||
await callback.answer("Напоминание не найдено", show_alert=True)
|
||
await state.clear()
|
||
return
|
||
|
||
await state.clear()
|
||
await callback.message.edit_text(
|
||
f"✅ Периодичность обновлена!\n\nТеперь: {format_interval_days(callback_data.days)}"
|
||
)
|
||
await callback.message.answer(
|
||
"Выбери действие из меню:",
|
||
reply_markup=get_main_menu_keyboard(),
|
||
)
|
||
await callback.answer("Обновлено!")
|
||
|
||
|
||
@router.message(EditReminderStates.editing_interval)
|
||
async def edit_interval_text(
|
||
message: Message,
|
||
state: FSMContext,
|
||
session: AsyncSession,
|
||
) -> None:
|
||
"""
|
||
Process interval input as text.
|
||
|
||
Args:
|
||
message: Telegram message
|
||
state: FSM state context
|
||
session: Database session
|
||
"""
|
||
days = validate_days_interval(message.text)
|
||
|
||
if days is None:
|
||
await message.answer("Некорректное значение. Введи целое положительное число дней:")
|
||
return
|
||
|
||
data = await state.get_data()
|
||
reminder_id = data["reminder_id"]
|
||
|
||
reminder = await reminders_service.update_reminder_interval(session, reminder_id, days)
|
||
|
||
if not reminder:
|
||
await message.answer("Напоминание не найдено")
|
||
await state.clear()
|
||
return
|
||
|
||
await state.clear()
|
||
await message.answer(
|
||
f"✅ Периодичность обновлена!\n\nТеперь: {format_interval_days(days)}",
|
||
reply_markup=get_main_menu_keyboard(),
|
||
)
|
||
|
||
|
||
@router.callback_query(
|
||
EditReminderStates.selecting_field,
|
||
ReminderEditCallback.filter(F.action == "time")
|
||
)
|
||
async def edit_time_start(
|
||
callback: CallbackQuery,
|
||
callback_data: ReminderEditCallback,
|
||
state: FSMContext,
|
||
) -> None:
|
||
"""
|
||
Start editing reminder time.
|
||
|
||
Args:
|
||
callback: Callback query
|
||
callback_data: Parsed callback data
|
||
state: FSM state context
|
||
"""
|
||
await state.set_state(EditReminderStates.editing_time)
|
||
await callback.message.edit_text(
|
||
"Введи новое время в формате ЧЧ:ММ (например, 09:00):"
|
||
)
|
||
await callback.answer()
|
||
|
||
|
||
@router.message(EditReminderStates.editing_time)
|
||
async def edit_time_process(
|
||
message: Message,
|
||
state: FSMContext,
|
||
session: AsyncSession,
|
||
) -> None:
|
||
"""
|
||
Process new reminder time.
|
||
|
||
Args:
|
||
message: Telegram message
|
||
state: FSM state context
|
||
session: Database session
|
||
"""
|
||
time_of_day = validate_time_format(message.text)
|
||
|
||
if time_of_day is None:
|
||
await message.answer(
|
||
"Некорректный формат времени. Используй формат ЧЧ:ММ (например, 09:00):"
|
||
)
|
||
return
|
||
|
||
data = await state.get_data()
|
||
reminder_id = data["reminder_id"]
|
||
|
||
reminder = await reminders_service.update_reminder_time(session, reminder_id, time_of_day)
|
||
|
||
if not reminder:
|
||
await message.answer("Напоминание не найдено")
|
||
await state.clear()
|
||
return
|
||
|
||
await state.clear()
|
||
await message.answer(
|
||
f"✅ Время обновлено!\n\nНовое время: {time_of_day.strftime('%H:%M')}",
|
||
reply_markup=get_main_menu_keyboard(),
|
||
)
|