"""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"π ΠΠ°ΠΏΠΎΠΌΠΈΠ½Π°Π½ΠΈΠ΅ #{reminder.id}\n\n"
f"Π’Π΅ΠΊΡΡ: {reminder.text}\n\n"
f"ΠΠ΅ΡΠΈΠΎΠ΄ΠΈΡΠ½ΠΎΡΡΡ: {format_interval_days(reminder.days_interval)}\n"
f"ΠΡΠ΅ΠΌΡ: {reminder.time_of_day.strftime('%H:%M')}\n"
f"Π‘ΡΠ°ΡΡΡ: {status}\n\n"
f"Π‘Π»Π΅Π΄ΡΡΡΠ΅Π΅ Π½Π°ΠΏΠΎΠΌΠΈΠ½Π°Π½ΠΈΠ΅: {format_datetime(reminder.next_run_at)}\n"
f"ΠΠΎΡΠ»Π΅Π΄Π½Π΅Π΅ Π²ΡΠΏΠΎΠ»Π½Π΅Π½ΠΈΠ΅: {last_done}\n"
f"ΠΡΠΏΠΎΠ»Π½Π΅Π½ΠΎ ΡΠ°Π·: {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()
@router.callback_query(ReminderActionCallback.filter(F.action == "back_to_list"))
async def back_to_reminders_list(
callback: CallbackQuery,
session: AsyncSession,
) -> None:
"""
Return to reminders list from details view.
Args:
callback: Callback query
session: Database session
"""
user = await UserService.ensure_user_exists(session, callback.from_user)
reminders = await reminders_service.get_user_all_reminders(session, user.id)
if not reminders:
await callback.message.edit_text(
"Π£ ΡΠ΅Π±Ρ ΠΏΠΎΠΊΠ° Π½Π΅Ρ Π½Π°ΠΏΠΎΠΌΠΈΠ½Π°Π½ΠΈΠΉ.\n\n"
"ΠΠ°ΠΆΠΌΠΈ Β«β ΠΠΎΠ²ΠΎΠ΅ Π½Π°ΠΏΠΎΠΌΠΈΠ½Π°Π½ΠΈΠ΅Β», ΡΡΠΎΠ±Ρ ΡΠΎΠ·Π΄Π°ΡΡ ΠΏΠ΅ΡΠ²ΠΎΠ΅!"
)
await callback.answer()
return
await callback.message.edit_text(
f"π Π’Π²ΠΎΠΈ Π½Π°ΠΏΠΎΠΌΠΈΠ½Π°Π½ΠΈΡ ({len(reminders)}):",
reply_markup=get_reminders_list_keyboard(reminders, page=0),
)
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"π ΠΠ°ΠΏΠΎΠΌΠΈΠ½Π°Π½ΠΈΠ΅ #{reminder.id}\n\n"
f"Π’Π΅ΠΊΡΡ: {reminder.text}\n\n"
f"ΠΠ΅ΡΠΈΠΎΠ΄ΠΈΡΠ½ΠΎΡΡΡ: {format_interval_days(reminder.days_interval)}\n"
f"ΠΡΠ΅ΠΌΡ: {reminder.time_of_day.strftime('%H:%M')}\n"
f"Π‘ΡΠ°ΡΡΡ: {status}\n\n"
f"Π‘Π»Π΅Π΄ΡΡΡΠ΅Π΅ Π½Π°ΠΏΠΎΠΌΠΈΠ½Π°Π½ΠΈΠ΅: {format_datetime(reminder.next_run_at)}\n"
f"ΠΠΎΡΠ»Π΅Π΄Π½Π΅Π΅ Π²ΡΠΏΠΎΠ»Π½Π΅Π½ΠΈΠ΅: {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(),
)