diff --git a/src/dutylog/application/bot/user_dialogs/admin_dialogs/admin_menu_dialog.py b/src/dutylog/application/bot/user_dialogs/admin_dialogs/admin_menu_dialog.py index 6e01344..449352f 100644 --- a/src/dutylog/application/bot/user_dialogs/admin_dialogs/admin_menu_dialog.py +++ b/src/dutylog/application/bot/user_dialogs/admin_dialogs/admin_menu_dialog.py @@ -1,1038 +1,52 @@ -from aiogram.types import User, Message, CallbackQuery -from aiogram import Bot -from aiogram.exceptions import TelegramForbiddenError, TelegramBadRequest -from aiogram_dialog import Dialog, Window, DialogManager -from aiogram_dialog.widgets.text import Format, Const -from aiogram_dialog.widgets.kbd import Row, SwitchTo, Button, ScrollingGroup, Select, Group -from aiogram_dialog.widgets.input import MessageInput -from magic_filter import F -from dishka import FromDishka -from dishka.integrations.aiogram_dialog import inject +from aiogram_dialog import Dialog -from dutylog.application.bot.user_dialogs.states import AdminMenuSG -from dutylog.infrastructure.database.repositories.users_repository import ( - UsersRepository, +from dutylog.application.bot.user_dialogs.admin_dialogs.main_menu import ( + main_menu_window, + statistics_window, ) -from dutylog.infrastructure.database.repositories.residents_repository import ( - ResidentsRepository, +from dutylog.application.bot.user_dialogs.admin_dialogs.residents_management import ( + residents_list_window, + resident_info_window, + resident_logout_confirm_window, + create_resident_name_window, + create_resident_floor_window, + create_resident_room_window, + create_resident_confirm_window, + search_input_window, + search_results_window, ) -from dutylog.infrastructure.database.repositories.rooms_repository import ( - RoomsRepository, +from dutylog.application.bot.user_dialogs.admin_dialogs.hours_management import ( + add_hours_select_window, + remove_hours_select_window, + add_hours_custom_window, + remove_hours_custom_window, + add_hours_confirm_window, + remove_hours_confirm_window, ) -from dutylog.infrastructure.database.repositories.floors_repository import ( - FloorsRepository, +from dutylog.application.bot.user_dialogs.admin_dialogs.broadcast import ( + broadcast_window, + broadcast_confirm_window, ) -from dutylog.infrastructure.database.repositories.hours_transactions_repository import ( - HoursTransactionsRepository, -) -from dutylog.infrastructure.utils.config import Config - - -@inject -async def get_admin_menu_data( - event_from_user: User, - users_repository: FromDishka[UsersRepository], - config: FromDishka[Config], - **kwargs, -): - user = await users_repository.get_or_create_user( - user_id=event_from_user.id, - username=event_from_user.username, - first_name=event_from_user.first_name, - last_name=event_from_user.last_name, - ) - - is_creator = event_from_user.id == config.bot.creator_id - - if is_creator: - greeting = "👑 Создатель" - else: - greeting = "👨💼 Администратор" - - content = f""" -{greeting} - -
📋 Панель управления- -Выберите действие: -""" - - return {"content": content} - - -@inject -async def get_statistics_data( - users_repository: FromDishka[UsersRepository], - residents_repository: FromDishka[ResidentsRepository], - **kwargs, -): - all_users = await users_repository.get_all_users() - all_residents = await residents_repository.get_all_residents() - - total_users = len(all_users) - total_residents = len(all_residents) - busy_residents = len([r for r in all_residents if r.is_busy]) - total_active_hours = sum(r.active_hours for r in all_residents) - total_inactive_hours = sum(r.inactive_hours for r in all_residents) - admins_count = len([u for u in all_users if u.is_admin]) - - stats_text = f""" -
📊 Статистика системы- -👥 Всего пользователей:
{total_users}
-👨💼 Администраторов: {admins_count}
-
-🏠 Всего резидентов: {total_residents}
-✅ Привязано к пользователям: {busy_residents}
-❌ Свободных: {total_residents - busy_residents}
-
-━━━━━━━━━━━━━━━━━━━━
-
-🟢 Всего отработанных часов: {total_inactive_hours} ч
-🔴 Всего неотработанных часов: {total_active_hours} ч
-📊 Общий итог: {total_active_hours + total_inactive_hours} ч
-"""
-
- return {"stats_content": stats_text}
-
-
-async def on_broadcast_message(
- message: Message,
- widget: MessageInput,
- dialog_manager: DialogManager,
-):
- dialog_manager.dialog_data["broadcast_message_id"] = message.message_id
- dialog_manager.dialog_data["broadcast_chat_id"] = message.chat.id
-
- await message.copy_to(message.chat.id)
- await dialog_manager.switch_to(AdminMenuSG.broadcast_confirm)
-
-
-@inject
-async def on_broadcast_confirm(
- callback: CallbackQuery,
- button: Button,
- dialog_manager: DialogManager,
- users_repository: FromDishka[UsersRepository],
-):
- assert callback.message is not None
-
- bot: Bot = dialog_manager.middleware_data["bot"]
- message_id = dialog_manager.dialog_data["broadcast_message_id"]
- chat_id = dialog_manager.dialog_data["broadcast_chat_id"]
- admin_id = callback.from_user.id
-
- all_users = await users_repository.get_all_users()
-
- success_count = 0
- failed_count = 0
-
- for user in all_users:
- if user.id == admin_id:
- continue
-
- try:
- await bot.copy_message(
- chat_id=user.id,
- from_chat_id=chat_id,
- message_id=message_id,
- )
- success_count += 1
- except TelegramForbiddenError:
- failed_count += 1
- except TelegramBadRequest:
- failed_count += 1
-
- result_text = f"""
-📢 Результаты рассылки- -✅ Успешно отправлено:
{success_count}
-❌ Не удалось отправить: {failed_count}
-📊 Всего пользователей: {len(all_users) - 1}
-"""
-
- await callback.message.answer(result_text)
- await callback.message.delete()
- await dialog_manager.start(AdminMenuSG.main)
-
-
-async def on_broadcast_cancel(
- callback: CallbackQuery,
- button: Button,
- dialog_manager: DialogManager,
-):
- await dialog_manager.switch_to(AdminMenuSG.main)
-
-
-@inject
-async def get_residents_list_data(
- residents_repository: FromDishka[ResidentsRepository],
- rooms_repository: FromDishka[RoomsRepository],
- **kwargs,
-):
- all_residents = await residents_repository.get_all_residents()
-
- residents_with_rooms = []
- for resident in all_residents:
- room = await rooms_repository.get_room_by_id(resident.room)
- room_number = room.number if room else 999999
- residents_with_rooms.append((resident, room_number))
-
- residents_with_rooms.sort(key=lambda x: x[1])
-
- residents_data = []
- for resident, room_number in residents_with_rooms:
- status = "🟢" if resident.is_busy else "⚪️"
- name = resident.real_name if resident.real_name else "Без имени"
-
- residents_data.append(
- (f"{name} | Комната {room_number} | {status}", resident.id)
- )
-
- content = f"""
-🏠 Резиденты- -Всего резидентов:
{len(all_residents)}
-
-Выберите резидента для просмотра информации:
-"""
-
- return {
- "content": content,
- "residents": residents_data,
- }
-
-
-@inject
-async def get_resident_info_data(
- dialog_manager: DialogManager,
- residents_repository: FromDishka[ResidentsRepository],
- rooms_repository: FromDishka[RoomsRepository],
- users_repository: FromDishka[UsersRepository],
- **kwargs,
-):
- resident_id = dialog_manager.dialog_data.get("selected_resident_id")
-
- if not resident_id:
- return {"info_content": "Ошибка: резидент не выбран", "is_busy": False, "from_search": False}
-
- resident = await residents_repository.get_resident_by_id(resident_id)
-
- if not resident:
- return {"info_content": "Ошибка: резидент не найден", "is_busy": False, "from_search": False}
-
- room = await rooms_repository.get_room_by_id(resident.room)
- room_number = room.number if room else "???"
-
- name = resident.real_name if resident.real_name else "Без имени"
- status = "🟢 Занят" if resident.is_busy else "⚪️ Свободен"
-
- user_info = "Не привязан"
- if resident.user_entity:
- user = await users_repository.get_user_by_id(resident.user_entity)
- if user:
- if user.username:
- username = f"@{user.username}"
- else:
- username = f"ID: {user.id}"
- user_info = f"{user.first_name} ({username})"
-
- info_content = f"""
-👤 Информация о резиденте- -ID:
{resident.id}
-Имя: {name}
-Комната: {room_number}
-Статус: {status}
-Пользователь: {user_info}
-
-━━━━━━━━━━━━━━━━━━━━
-
-🟢 Отработанные часы: {resident.inactive_hours} ч
-🔴 Неотработанные часы: {resident.active_hours} ч
-"""
-
- from_search = dialog_manager.dialog_data.get("from_search", False)
-
- return {
- "info_content": info_content,
- "is_busy": resident.is_busy,
- "from_search": from_search,
- }
-
-
-async def on_resident_selected(
- callback: CallbackQuery,
- widget: Select,
- dialog_manager: DialogManager,
- item_id: str,
-):
- dialog_manager.dialog_data["selected_resident_id"] = int(item_id)
- dialog_manager.dialog_data["from_search"] = False
- await dialog_manager.switch_to(AdminMenuSG.resident_info)
-
-
-async def on_add_resident(
- callback: CallbackQuery,
- button: Button,
- dialog_manager: DialogManager,
-):
- await dialog_manager.switch_to(AdminMenuSG.create_resident_name)
-
-
-async def on_resident_name_input(
- message: Message,
- widget: MessageInput,
- dialog_manager: DialogManager,
-):
- if not message.text or len(message.text.strip()) < 2:
- await message.answer("⚠️ Пожалуйста, введите корректное имя и фамилию")
- return
-
- dialog_manager.dialog_data["new_resident_name"] = message.text.strip()
- await dialog_manager.switch_to(AdminMenuSG.create_resident_floor)
-
-
-@inject
-async def get_create_resident_floors_data(
- floors_repository: FromDishka[FloorsRepository],
- **kwargs,
-):
- all_floors = await floors_repository.get_all_floors()
- all_floors.sort(key=lambda f: f.number)
-
- return {
- "floors": [(f.id, f"Этаж {f.number}") for f in all_floors],
- }
-
-
-async def on_create_resident_floor_selected(
- callback: CallbackQuery,
- widget: Select,
- dialog_manager: DialogManager,
- item_id: str,
-):
- dialog_manager.dialog_data["new_resident_floor_id"] = int(item_id)
- await dialog_manager.switch_to(AdminMenuSG.create_resident_room)
-
-
-@inject
-async def get_create_resident_rooms_data(
- dialog_manager: DialogManager,
- rooms_repository: FromDishka[RoomsRepository],
- **kwargs,
-):
- floor_id = dialog_manager.dialog_data.get("new_resident_floor_id")
-
- if not floor_id:
- return {"rooms": []}
-
- rooms = await rooms_repository.get_rooms_by_floor(floor_id)
- rooms.sort(key=lambda r: r.number)
-
- return {
- "rooms": [(r.id, str(r.number)) for r in rooms],
- }
-
-
-async def on_create_resident_room_selected(
- callback: CallbackQuery,
- widget: Select,
- dialog_manager: DialogManager,
- item_id: str,
-):
- dialog_manager.dialog_data["new_resident_room_id"] = int(item_id)
- await dialog_manager.switch_to(AdminMenuSG.create_resident_confirm)
-
-
-async def get_create_resident_confirm_data(
- dialog_manager: DialogManager,
- **kwargs,
-):
- name = dialog_manager.dialog_data.get("new_resident_name", "???")
- return {"resident_name": name}
-
-
-@inject
-async def on_create_resident_confirm(
- callback: CallbackQuery,
- button: Button,
- dialog_manager: DialogManager,
- residents_repository: FromDishka[ResidentsRepository],
-):
- name = dialog_manager.dialog_data.get("new_resident_name")
- room_id = dialog_manager.dialog_data.get("new_resident_room_id")
-
- if name and room_id:
- await residents_repository.create_resident(
- room_id=room_id,
- real_name=name,
- )
- await callback.answer("✅ Резидент создан!")
-
- await dialog_manager.switch_to(AdminMenuSG.residents)
-
-
-async def on_create_resident_cancel(
- callback: CallbackQuery,
- button: Button,
- dialog_manager: DialogManager,
-):
- await dialog_manager.switch_to(AdminMenuSG.residents)
-
-
-async def on_filter_residents(
- callback: CallbackQuery,
- button: Button,
- dialog_manager: DialogManager,
-):
- await callback.answer("⚠️ Функционал в разработке", show_alert=True)
-
-
-async def on_search_residents(
- callback: CallbackQuery,
- button: Button,
- dialog_manager: DialogManager,
-):
- await dialog_manager.switch_to(AdminMenuSG.residents_search_input)
-
-
-async def on_rooms_click(
- callback: CallbackQuery,
- button: Button,
- dialog_manager: DialogManager,
-):
- await callback.answer("⚠️ Функционал в разработке", show_alert=True)
-
-
-async def on_floors_click(
- callback: CallbackQuery,
- button: Button,
- dialog_manager: DialogManager,
-):
- await callback.answer("⚠️ Функционал в разработке", show_alert=True)
-
-
-@inject
-async def on_logout_resident_confirm(
- callback: CallbackQuery,
- button: Button,
- dialog_manager: DialogManager,
- residents_repository: FromDishka[ResidentsRepository],
-):
- resident_id = dialog_manager.dialog_data.get("selected_resident_id")
-
- if resident_id:
- await residents_repository.unbind_user_from_resident(resident_id)
-
- await dialog_manager.switch_to(AdminMenuSG.resident_info)
-
-
-async def on_logout_resident_cancel(
- callback: CallbackQuery,
- button: Button,
- dialog_manager: DialogManager,
-):
- await dialog_manager.switch_to(AdminMenuSG.resident_info)
-
-
-async def on_add_hours_click(
- callback: CallbackQuery,
- button: Button,
- dialog_manager: DialogManager,
-):
- await dialog_manager.switch_to(AdminMenuSG.add_hours_select)
-
-
-async def on_remove_hours_click(
- callback: CallbackQuery,
- button: Button,
- dialog_manager: DialogManager,
-):
- await dialog_manager.switch_to(AdminMenuSG.remove_hours_select)
-
-
-async def get_hours_select_data(**kwargs):
- hours_options = [
- (5, "5"), (10, "10"), (15, "15"), (20, "20"),
- (25, "25"), (30, "30"), (35, "35"), (40, "40"),
- (45, "45"), (50, "50"), (55, "55"), (60, "60"),
- (65, "65"), (70, "70"), (75, "75"), (80, "80"),
- ]
- return {"hours_options": hours_options}
-
-
-async def on_hours_selected(
- callback: CallbackQuery,
- widget: Select,
- dialog_manager: DialogManager,
- item_id: str,
-):
- dialog_manager.dialog_data["selected_hours"] = int(item_id)
-
- if dialog_manager.current_context().state == AdminMenuSG.add_hours_select:
- await dialog_manager.switch_to(AdminMenuSG.add_hours_confirm)
- else:
- await dialog_manager.switch_to(AdminMenuSG.remove_hours_confirm)
-
-
-async def on_custom_hours_click(
- callback: CallbackQuery,
- button: Button,
- dialog_manager: DialogManager,
-):
- if dialog_manager.current_context().state == AdminMenuSG.add_hours_select:
- await dialog_manager.switch_to(AdminMenuSG.add_hours_custom)
- else:
- await dialog_manager.switch_to(AdminMenuSG.remove_hours_custom)
-
-
-async def on_custom_hours_input(
- message: Message,
- widget: MessageInput,
- dialog_manager: DialogManager,
-):
- if not message.text:
- await message.answer("⚠️ Пожалуйста, введите число")
- return
-
- try:
- hours = int(message.text)
- if hours <= 0:
- await message.answer("⚠️ Количество часов должно быть положительным числом")
- return
-
- dialog_manager.dialog_data["selected_hours"] = hours
-
- if dialog_manager.current_context().state == AdminMenuSG.add_hours_custom:
- await dialog_manager.switch_to(AdminMenuSG.add_hours_confirm)
- else:
- await dialog_manager.switch_to(AdminMenuSG.remove_hours_confirm)
- except ValueError:
- await message.answer("⚠️ Пожалуйста, введите корректное число")
-
-
-async def get_hours_confirm_data(
- dialog_manager: DialogManager,
- **kwargs,
-):
- hours = dialog_manager.dialog_data.get("selected_hours", 0)
- return {"hours": hours}
-
-
-@inject
-async def on_add_hours_confirm(
- callback: CallbackQuery,
- button: Button,
- dialog_manager: DialogManager,
- transactions_repository: FromDishka[HoursTransactionsRepository],
-):
- resident_id = dialog_manager.dialog_data.get("selected_resident_id")
- hours = dialog_manager.dialog_data.get("selected_hours")
- admin_id = callback.from_user.id
-
- if resident_id and hours:
- # Добавляем часы к неотработанным (active_hours)
- await transactions_repository.add_hours(
- resident_id=resident_id,
- amount=hours,
- admin_id=admin_id,
- is_active=True,
- )
-
- await dialog_manager.switch_to(AdminMenuSG.resident_info)
-
-
-@inject
-async def on_remove_hours_confirm(
- callback: CallbackQuery,
- button: Button,
- dialog_manager: DialogManager,
- transactions_repository: FromDishka[HoursTransactionsRepository],
- residents_repository: FromDishka[ResidentsRepository],
-):
- resident_id = dialog_manager.dialog_data.get("selected_resident_id")
- hours = dialog_manager.dialog_data.get("selected_hours")
- admin_id = callback.from_user.id
-
- if resident_id and hours:
- resident = await residents_repository.get_resident_by_id(resident_id)
- if resident and resident.active_hours < hours:
- await callback.answer(
- f"⚠️ Недостаточно часов! У резидента {resident.active_hours} неотработанных ч, а вы пытаетесь отнять {hours} ч",
- show_alert=True
- )
- await dialog_manager.switch_to(AdminMenuSG.resident_info)
- return
-
- # Перемещаем часы из неотработанных в отработанные
- await transactions_repository.move_hours_to_completed(
- resident_id=resident_id,
- amount=hours,
- admin_id=admin_id,
- )
-
- await dialog_manager.switch_to(AdminMenuSG.resident_info)
-
-
-async def on_hours_cancel(
- callback: CallbackQuery,
- button: Button,
- dialog_manager: DialogManager,
-):
- await dialog_manager.switch_to(AdminMenuSG.resident_info)
-
-
-async def on_search_input(
- message: Message,
- widget: MessageInput,
- dialog_manager: DialogManager,
-):
- if not message.text or len(message.text.strip()) < 1:
- await message.answer("⚠️ Пожалуйста, введите поисковый запрос")
- return
-
- query = message.text.strip()
- if query.startswith("@"):
- query = query[1:]
-
- dialog_manager.dialog_data["search_query"] = query
- dialog_manager.dialog_data["is_search_active"] = True
- await dialog_manager.switch_to(AdminMenuSG.residents_search_results)
-
-
-@inject
-async def get_search_results_data(
- dialog_manager: DialogManager,
- residents_repository: FromDishka[ResidentsRepository],
- rooms_repository: FromDishka[RoomsRepository],
- users_repository: FromDishka[UsersRepository],
- **kwargs,
-):
- query = dialog_manager.dialog_data.get("search_query", "")
-
- residents, search_type = await residents_repository.search_residents(
- query, users_repository
- )
-
- if not residents:
- return {
- "content": f"""
-🔍 Результаты поиска- -Запрос:
{query}
-
-❌ Ничего не найдено
-""",
- "residents": [],
- "has_results": False,
- }
-
- residents_with_rooms = []
- for resident in residents:
- room = await rooms_repository.get_room_by_id(resident.room)
- room_number = room.number if room else 999999
- residents_with_rooms.append((resident, room_number))
-
- residents_with_rooms.sort(key=lambda x: x[1])
-
- residents_data = []
- for resident, room_number in residents_with_rooms:
- status = "🟢" if resident.is_busy else "⚪️"
- name = resident.real_name if resident.real_name else "Без имени"
-
- residents_data.append(
- (f"{name} | Комната {room_number} | {status}", resident.id)
- )
-
- content = f"""
-🔍 Результаты поиска по {search_type}- -Найдено резидентов:
{len(residents)}
-
-Выберите резидента для просмотра информации:
-"""
-
- return {
- "content": content,
- "residents": residents_data,
- "has_results": True,
- }
-
-
-async def on_search_resident_selected(
- callback: CallbackQuery,
- widget: Select,
- dialog_manager: DialogManager,
- item_id: str,
-):
- dialog_manager.dialog_data["selected_resident_id"] = int(item_id)
- dialog_manager.dialog_data["from_search"] = True
- await dialog_manager.switch_to(AdminMenuSG.resident_info)
admin_menu_dialog = Dialog(
- Window(
- Format("{content}"),
- SwitchTo(
- Const("🏠 Резиденты"),
- id="residents_btn",
- state=AdminMenuSG.residents,
- ),
- Row(
- Button(
- Const("🚪 Комнаты"),
- id="rooms_btn",
- on_click=on_rooms_click,
- ),
- Button(
- Const("🏢 Этажи"),
- id="floors_btn",
- on_click=on_floors_click,
- ),
- ),
- SwitchTo(
- Const("📊 Статистика"),
- id="stats_btn",
- state=AdminMenuSG.statistics,
- ),
- SwitchTo(
- Const("📢 Рассылка"),
- id="broadcast_btn",
- state=AdminMenuSG.broadcast,
- ),
- state=AdminMenuSG.main,
- getter=get_admin_menu_data,
- ),
- Window(
- Format("{content}"),
- Row(
- Button(
- Const("🔍 Поиск"),
- id="search_residents_btn",
- on_click=on_search_residents,
- ),
- Button(
- Const("🔽 Фильтр"),
- id="filter_residents_btn",
- on_click=on_filter_residents,
- ),
- ),
- ScrollingGroup(
- Select(
- Format("{item[0]}"),
- id="residents_select",
- item_id_getter=lambda x: x[1],
- items="residents",
- on_click=on_resident_selected,
- ),
- id="residents_scroll",
- width=1,
- height=7,
- ),
- Button(
- Const("➕ Добавить резидента"),
- id="add_resident_btn",
- on_click=on_add_resident,
- ),
- SwitchTo(
- Const("◀️ Назад"),
- id="back_to_admin_menu",
- state=AdminMenuSG.main,
- ),
- state=AdminMenuSG.residents,
- getter=get_residents_list_data,
- ),
- Window(
- Format("{info_content}"),
- Row(
- Button(
- Const("Добавить часы"),
- id="add_hours_btn",
- on_click=on_add_hours_click,
- ),
- Button(
- Const("Отнять часы"),
- id="remove_hours_btn",
- on_click=on_remove_hours_click,
- ),
- ),
- Button(
- Const("🚪 Разлогинить"),
- id="logout_resident_btn",
- on_click=lambda c, b, m: m.switch_to(AdminMenuSG.resident_logout_confirm),
- when="is_busy",
- ),
- SwitchTo(
- Const("◀️ Назад к результатам"),
- id="back_to_search_results",
- state=AdminMenuSG.residents_search_results,
- when="from_search",
- ),
- SwitchTo(
- Const("◀️ Назад к списку"),
- id="back_to_residents_list",
- state=AdminMenuSG.residents,
- when=~F["from_search"],
- ),
- state=AdminMenuSG.resident_info,
- getter=get_resident_info_data,
- ),
- Window(
- Const(
- "⚠️ Подтверждение\n\nВы уверены, что хотите разлогинить этого резидента?" - ), - Row( - Button( - Const("✅ Да"), - id="confirm_logout", - on_click=on_logout_resident_confirm, - ), - Button( - Const("❌ Нет"), - id="cancel_logout", - on_click=on_logout_resident_cancel, - ), - ), - state=AdminMenuSG.resident_logout_confirm, - ), - Window( - Const("
➕ Добавить часы\n\nВыберите количество часов:"), - Group( - Select( - Format("{item[1]} ч"), - id="hours_select_add", - item_id_getter=lambda x: x[0], - items="hours_options", - on_click=on_hours_selected, - ), - width=4, - ), - Button( - Const("✏️ Ввести свое количество"), - id="custom_hours_add_btn", - on_click=on_custom_hours_click, - ), - SwitchTo( - Const("◀️ Отмена"), - id="cancel_add_hours", - state=AdminMenuSG.resident_info, - ), - state=AdminMenuSG.add_hours_select, - getter=get_hours_select_data, - ), - Window( - Const("
➖ Отнять часы\n\nВыберите количество часов:"), - Group( - Select( - Format("{item[1]} ч"), - id="hours_select_remove", - item_id_getter=lambda x: x[0], - items="hours_options", - on_click=on_hours_selected, - ), - width=4, - ), - Button( - Const("✏️ Ввести свое количество"), - id="custom_hours_remove_btn", - on_click=on_custom_hours_click, - ), - SwitchTo( - Const("◀️ Отмена"), - id="cancel_remove_hours", - state=AdminMenuSG.resident_info, - ), - state=AdminMenuSG.remove_hours_select, - getter=get_hours_select_data, - ), - Window( - Const("
✏️ Добавить часы\n\nВведите количество часов:"), - MessageInput(on_custom_hours_input), - SwitchTo( - Const("◀️ Отмена"), - id="cancel_custom_add", - state=AdminMenuSG.add_hours_select, - ), - state=AdminMenuSG.add_hours_custom, - ), - Window( - Const("
✏️ Отнять часы\n\nВведите количество часов:"), - MessageInput(on_custom_hours_input), - SwitchTo( - Const("◀️ Отмена"), - id="cancel_custom_remove", - state=AdminMenuSG.remove_hours_select, - ), - state=AdminMenuSG.remove_hours_custom, - ), - Window( - Format("
➕ Подтверждение\n\nВы уверены, что хотите добавить
{hours} часов?"),
- Row(
- Button(
- Const("✅ Да"),
- id="confirm_add_hours",
- on_click=on_add_hours_confirm,
- ),
- Button(
- Const("❌ Нет"),
- id="cancel_add_hours_confirm",
- on_click=on_hours_cancel,
- ),
- ),
- state=AdminMenuSG.add_hours_confirm,
- getter=get_hours_confirm_data,
- ),
- Window(
- Format("➖ Подтверждение\n\nВы уверены, что хотите отнять
{hours} часов?"),
- Row(
- Button(
- Const("✅ Да"),
- id="confirm_remove_hours",
- on_click=on_remove_hours_confirm,
- ),
- Button(
- Const("❌ Нет"),
- id="cancel_remove_hours_confirm",
- on_click=on_hours_cancel,
- ),
- ),
- state=AdminMenuSG.remove_hours_confirm,
- getter=get_hours_confirm_data,
- ),
- Window(
- Const("➕ Создание резидента\n\nВведите имя и фамилию резидента:"), - MessageInput(on_resident_name_input), - SwitchTo( - Const("◀️ Отмена"), - id="cancel_create_resident_name", - state=AdminMenuSG.residents, - ), - state=AdminMenuSG.create_resident_name, - ), - Window( - Const("
🏢 Выбор этажа\n\nВыберите этаж для нового резидента:"), - Group( - Select( - Format("{item[1]}"), - id="create_resident_floor_select", - item_id_getter=lambda x: x[0], - items="floors", - on_click=on_create_resident_floor_selected, - ), - width=2, - ), - SwitchTo( - Const("◀️ Назад"), - id="back_to_create_name", - state=AdminMenuSG.create_resident_name, - ), - state=AdminMenuSG.create_resident_floor, - getter=get_create_resident_floors_data, - ), - Window( - Const("
🚪 Выбор комнаты\n\nВыберите комнату для нового резидента:"), - Group( - Select( - Format("{item[1]}"), - id="create_resident_room_select", - item_id_getter=lambda x: x[0], - items="rooms", - on_click=on_create_resident_room_selected, - ), - width=3, - ), - SwitchTo( - Const("◀️ Назад"), - id="back_to_create_floor", - state=AdminMenuSG.create_resident_floor, - ), - state=AdminMenuSG.create_resident_room, - getter=get_create_resident_rooms_data, - ), - Window( - Format("
✅ Подтверждение\n\nСоздать резидента {resident_name}?"), - Row( - Button( - Const("✅ Да"), - id="confirm_create_resident", - on_click=on_create_resident_confirm, - ), - Button( - Const("❌ Нет"), - id="cancel_create_resident", - on_click=on_create_resident_cancel, - ), - ), - state=AdminMenuSG.create_resident_confirm, - getter=get_create_resident_confirm_data, - ), - Window( - Format("{stats_content}"), - SwitchTo(Const("◀️ Назад"), id="back_from_stats", state=AdminMenuSG.main), - state=AdminMenuSG.statistics, - getter=get_statistics_data, - ), - Window( - Const( - "
📢 Рассылка\n\nОтправьте сообщение, которое хотите разослать всем пользователям:" - ), - MessageInput(on_broadcast_message), - SwitchTo(Const("◀️ Отмена"), id="cancel_broadcast", state=AdminMenuSG.main), - state=AdminMenuSG.broadcast, - ), - Window( - Const( - "
📢 Подтверждение рассылки\n\n⚠️ Вы уверены, что хотите отправить это сообщение всем пользователям?" - ), - Row( - Button( - Const("✅ Да"), id="confirm_broadcast", on_click=on_broadcast_confirm - ), - Button( - Const("❌ Нет"), - id="cancel_broadcast_confirm", - on_click=on_broadcast_cancel, - ), - ), - state=AdminMenuSG.broadcast_confirm, - ), - Window( - Const("
🔍 Поиск резидентов\n\nВведите номер комнаты, имя резидента или username пользователя. Можно указать только часть имени или username для поиска."), - MessageInput(on_search_input), - SwitchTo( - Const("◀️ Отмена"), - id="cancel_search", - state=AdminMenuSG.residents, - ), - state=AdminMenuSG.residents_search_input, - ), - Window( - Format("{content}"), - ScrollingGroup( - Select( - Format("{item[0]}"), - id="search_residents_select", - item_id_getter=lambda x: x[1], - items="residents", - on_click=on_search_resident_selected, - ), - id="search_residents_scroll", - width=1, - height=7, - when="has_results", - ), - SwitchTo( - Const("🔍 Новый поиск"), - id="new_search", - state=AdminMenuSG.residents_search_input, - ), - SwitchTo( - Const("◀️ К списку резидентов"), - id="back_to_residents_from_search", - state=AdminMenuSG.residents, - ), - state=AdminMenuSG.residents_search_results, - getter=get_search_results_data, - ), + main_menu_window, + residents_list_window, + resident_info_window, + resident_logout_confirm_window, + add_hours_select_window, + remove_hours_select_window, + add_hours_custom_window, + remove_hours_custom_window, + add_hours_confirm_window, + remove_hours_confirm_window, + create_resident_name_window, + create_resident_floor_window, + create_resident_room_window, + create_resident_confirm_window, + statistics_window, + broadcast_window, + broadcast_confirm_window, + search_input_window, + search_results_window, ) diff --git a/src/dutylog/application/bot/user_dialogs/admin_dialogs/broadcast.py b/src/dutylog/application/bot/user_dialogs/admin_dialogs/broadcast.py new file mode 100644 index 0000000..21649f5 --- /dev/null +++ b/src/dutylog/application/bot/user_dialogs/admin_dialogs/broadcast.py @@ -0,0 +1,109 @@ +from aiogram.types import Message, CallbackQuery +from aiogram import Bot +from aiogram.exceptions import TelegramForbiddenError, TelegramBadRequest +from aiogram_dialog import Window, DialogManager +from aiogram_dialog.widgets.text import Const +from aiogram_dialog.widgets.kbd import Row, SwitchTo, Button +from aiogram_dialog.widgets.input import MessageInput +from dishka import FromDishka +from dishka.integrations.aiogram_dialog import inject + +from dutylog.application.bot.user_dialogs.states import AdminMenuSG +from dutylog.infrastructure.database.repositories.users_repository import ( + UsersRepository, +) + + +async def on_broadcast_message( + message: Message, + widget: MessageInput, + dialog_manager: DialogManager, +): + dialog_manager.dialog_data["broadcast_message_id"] = message.message_id + dialog_manager.dialog_data["broadcast_chat_id"] = message.chat.id + + await message.copy_to(message.chat.id) + await dialog_manager.switch_to(AdminMenuSG.broadcast_confirm) + + +@inject +async def on_broadcast_confirm( + callback: CallbackQuery, + button: Button, + dialog_manager: DialogManager, + users_repository: FromDishka[UsersRepository], +): + assert callback.message is not None + + bot: Bot = dialog_manager.middleware_data["bot"] + message_id = dialog_manager.dialog_data["broadcast_message_id"] + chat_id = dialog_manager.dialog_data["broadcast_chat_id"] + admin_id = callback.from_user.id + + all_users = await users_repository.get_all_users() + + success_count = 0 + failed_count = 0 + + for user in all_users: + if user.id == admin_id: + continue + + try: + await bot.copy_message( + chat_id=user.id, + from_chat_id=chat_id, + message_id=message_id, + ) + success_count += 1 + except TelegramForbiddenError: + failed_count += 1 + except TelegramBadRequest: + failed_count += 1 + + result_text = f""" +
📢 Результаты рассылки+ +✅ Успешно отправлено:
{success_count}
+❌ Не удалось отправить: {failed_count}
+📊 Всего пользователей: {len(all_users) - 1}
+"""
+
+ await callback.message.answer(result_text)
+ await callback.message.delete()
+ await dialog_manager.start(AdminMenuSG.main)
+
+
+async def on_broadcast_cancel(
+ callback: CallbackQuery,
+ button: Button,
+ dialog_manager: DialogManager,
+):
+ await dialog_manager.switch_to(AdminMenuSG.main)
+
+
+broadcast_window = Window(
+ Const(
+ "📢 Рассылка\n\nОтправьте сообщение, которое хотите разослать всем пользователям:" + ), + MessageInput(on_broadcast_message), + SwitchTo(Const("◀️ Отмена"), id="cancel_broadcast", state=AdminMenuSG.main), + state=AdminMenuSG.broadcast, +) + +broadcast_confirm_window = Window( + Const( + "
📢 Подтверждение рассылки\n\n⚠️ Вы уверены, что хотите отправить это сообщение всем пользователям?" + ), + Row( + Button( + Const("✅ Да"), id="confirm_broadcast", on_click=on_broadcast_confirm + ), + Button( + Const("❌ Нет"), + id="cancel_broadcast_confirm", + on_click=on_broadcast_cancel, + ), + ), + state=AdminMenuSG.broadcast_confirm, +) diff --git a/src/dutylog/application/bot/user_dialogs/admin_dialogs/hours_management.py b/src/dutylog/application/bot/user_dialogs/admin_dialogs/hours_management.py new file mode 100644 index 0000000..8145ab3 --- /dev/null +++ b/src/dutylog/application/bot/user_dialogs/admin_dialogs/hours_management.py @@ -0,0 +1,271 @@ +from aiogram.types import Message, CallbackQuery +from aiogram_dialog import Window, DialogManager +from aiogram_dialog.widgets.text import Format, Const +from aiogram_dialog.widgets.kbd import Row, SwitchTo, Button, Select, Group +from aiogram_dialog.widgets.input import MessageInput +from dishka import FromDishka +from dishka.integrations.aiogram_dialog import inject + +from dutylog.application.bot.user_dialogs.states import AdminMenuSG +from dutylog.infrastructure.database.repositories.residents_repository import ( + ResidentsRepository, +) +from dutylog.infrastructure.database.repositories.hours_transactions_repository import ( + HoursTransactionsRepository, +) + + +async def on_add_hours_click( + callback: CallbackQuery, + button: Button, + dialog_manager: DialogManager, +): + await dialog_manager.switch_to(AdminMenuSG.add_hours_select) + + +async def on_remove_hours_click( + callback: CallbackQuery, + button: Button, + dialog_manager: DialogManager, +): + await dialog_manager.switch_to(AdminMenuSG.remove_hours_select) + + +async def get_hours_select_data(**kwargs): + hours_options = [ + (5, "5"), (10, "10"), (15, "15"), (20, "20"), + (25, "25"), (30, "30"), (35, "35"), (40, "40"), + (45, "45"), (50, "50"), (55, "55"), (60, "60"), + (65, "65"), (70, "70"), (75, "75"), (80, "80"), + ] + return {"hours_options": hours_options} + + +async def on_hours_selected( + callback: CallbackQuery, + widget: Select, + dialog_manager: DialogManager, + item_id: str, +): + dialog_manager.dialog_data["selected_hours"] = int(item_id) + + if dialog_manager.current_context().state == AdminMenuSG.add_hours_select: + await dialog_manager.switch_to(AdminMenuSG.add_hours_confirm) + else: + await dialog_manager.switch_to(AdminMenuSG.remove_hours_confirm) + + +async def on_custom_hours_click( + callback: CallbackQuery, + button: Button, + dialog_manager: DialogManager, +): + if dialog_manager.current_context().state == AdminMenuSG.add_hours_select: + await dialog_manager.switch_to(AdminMenuSG.add_hours_custom) + else: + await dialog_manager.switch_to(AdminMenuSG.remove_hours_custom) + + +async def on_custom_hours_input( + message: Message, + widget: MessageInput, + dialog_manager: DialogManager, +): + if not message.text: + await message.answer("⚠️ Пожалуйста, введите число") + return + + try: + hours = int(message.text) + if hours <= 0: + await message.answer("⚠️ Количество часов должно быть положительным числом") + return + + dialog_manager.dialog_data["selected_hours"] = hours + + if dialog_manager.current_context().state == AdminMenuSG.add_hours_custom: + await dialog_manager.switch_to(AdminMenuSG.add_hours_confirm) + else: + await dialog_manager.switch_to(AdminMenuSG.remove_hours_confirm) + except ValueError: + await message.answer("⚠️ Пожалуйста, введите корректное число") + + +async def get_hours_confirm_data( + dialog_manager: DialogManager, + **kwargs, +): + hours = dialog_manager.dialog_data.get("selected_hours", 0) + return {"hours": hours} + + +@inject +async def on_add_hours_confirm( + callback: CallbackQuery, + button: Button, + dialog_manager: DialogManager, + transactions_repository: FromDishka[HoursTransactionsRepository], +): + resident_id = dialog_manager.dialog_data.get("selected_resident_id") + hours = dialog_manager.dialog_data.get("selected_hours") + admin_id = callback.from_user.id + + if resident_id and hours: + await transactions_repository.add_hours( + resident_id=resident_id, + amount=hours, + admin_id=admin_id, + is_active=True, + ) + + await dialog_manager.switch_to(AdminMenuSG.resident_info) + + +@inject +async def on_remove_hours_confirm( + callback: CallbackQuery, + button: Button, + dialog_manager: DialogManager, + transactions_repository: FromDishka[HoursTransactionsRepository], + residents_repository: FromDishka[ResidentsRepository], +): + resident_id = dialog_manager.dialog_data.get("selected_resident_id") + hours = dialog_manager.dialog_data.get("selected_hours") + admin_id = callback.from_user.id + + if resident_id and hours: + resident = await residents_repository.get_resident_by_id(resident_id) + if resident and resident.active_hours < hours: + await callback.answer( + f"⚠️ Недостаточно часов! У резидента {resident.active_hours} неотработанных ч, а вы пытаетесь отнять {hours} ч", + show_alert=True + ) + await dialog_manager.switch_to(AdminMenuSG.resident_info) + return + + await transactions_repository.move_hours_to_completed( + resident_id=resident_id, + amount=hours, + admin_id=admin_id, + ) + + await dialog_manager.switch_to(AdminMenuSG.resident_info) + + +async def on_hours_cancel( + callback: CallbackQuery, + button: Button, + dialog_manager: DialogManager, +): + await dialog_manager.switch_to(AdminMenuSG.resident_info) + + +add_hours_select_window = Window( + Const("
➕ Добавить часы\n\nВыберите количество часов:"), + Group( + Select( + Format("{item[1]} ч"), + id="hours_select_add", + item_id_getter=lambda x: x[0], + items="hours_options", + on_click=on_hours_selected, + ), + width=4, + ), + Button( + Const("✏️ Ввести свое количество"), + id="custom_hours_add_btn", + on_click=on_custom_hours_click, + ), + SwitchTo( + Const("◀️ Отмена"), + id="cancel_add_hours", + state=AdminMenuSG.resident_info, + ), + state=AdminMenuSG.add_hours_select, + getter=get_hours_select_data, +) + +remove_hours_select_window = Window( + Const("
➖ Отнять часы\n\nВыберите количество часов:"), + Group( + Select( + Format("{item[1]} ч"), + id="hours_select_remove", + item_id_getter=lambda x: x[0], + items="hours_options", + on_click=on_hours_selected, + ), + width=4, + ), + Button( + Const("✏️ Ввести свое количество"), + id="custom_hours_remove_btn", + on_click=on_custom_hours_click, + ), + SwitchTo( + Const("◀️ Отмена"), + id="cancel_remove_hours", + state=AdminMenuSG.resident_info, + ), + state=AdminMenuSG.remove_hours_select, + getter=get_hours_select_data, +) + +add_hours_custom_window = Window( + Const("
✏️ Добавить часы\n\nВведите количество часов:"), + MessageInput(on_custom_hours_input), + SwitchTo( + Const("◀️ Отмена"), + id="cancel_custom_add", + state=AdminMenuSG.add_hours_select, + ), + state=AdminMenuSG.add_hours_custom, +) + +remove_hours_custom_window = Window( + Const("
✏️ Отнять часы\n\nВведите количество часов:"), + MessageInput(on_custom_hours_input), + SwitchTo( + Const("◀️ Отмена"), + id="cancel_custom_remove", + state=AdminMenuSG.remove_hours_select, + ), + state=AdminMenuSG.remove_hours_custom, +) + +add_hours_confirm_window = Window( + Format("
➕ Подтверждение\n\nВы уверены, что хотите добавить
{hours} часов?"),
+ Row(
+ Button(
+ Const("✅ Да"),
+ id="confirm_add_hours",
+ on_click=on_add_hours_confirm,
+ ),
+ Button(
+ Const("❌ Нет"),
+ id="cancel_add_hours_confirm",
+ on_click=on_hours_cancel,
+ ),
+ ),
+ state=AdminMenuSG.add_hours_confirm,
+ getter=get_hours_confirm_data,
+)
+
+remove_hours_confirm_window = Window(
+ Format("➖ Подтверждение\n\nВы уверены, что хотите отнять
{hours} часов?"),
+ Row(
+ Button(
+ Const("✅ Да"),
+ id="confirm_remove_hours",
+ on_click=on_remove_hours_confirm,
+ ),
+ Button(
+ Const("❌ Нет"),
+ id="cancel_remove_hours_confirm",
+ on_click=on_hours_cancel,
+ ),
+ ),
+ state=AdminMenuSG.remove_hours_confirm,
+ getter=get_hours_confirm_data,
+)
diff --git a/src/dutylog/application/bot/user_dialogs/admin_dialogs/main_menu.py b/src/dutylog/application/bot/user_dialogs/admin_dialogs/main_menu.py
new file mode 100644
index 0000000..89f3eee
--- /dev/null
+++ b/src/dutylog/application/bot/user_dialogs/admin_dialogs/main_menu.py
@@ -0,0 +1,132 @@
+from aiogram.types import User
+from aiogram_dialog import Window, DialogManager
+from aiogram_dialog.widgets.text import Format, Const
+from aiogram_dialog.widgets.kbd import Row, SwitchTo, Button
+from dishka import FromDishka
+from dishka.integrations.aiogram_dialog import inject
+
+from dutylog.application.bot.user_dialogs.states import AdminMenuSG
+from dutylog.infrastructure.database.repositories.users_repository import (
+ UsersRepository,
+)
+from dutylog.infrastructure.database.repositories.residents_repository import (
+ ResidentsRepository,
+)
+from dutylog.infrastructure.utils.config import Config
+
+
+@inject
+async def get_admin_menu_data(
+ event_from_user: User,
+ users_repository: FromDishka[UsersRepository],
+ config: FromDishka[Config],
+ **kwargs,
+):
+ user = await users_repository.get_or_create_user(
+ user_id=event_from_user.id,
+ username=event_from_user.username,
+ first_name=event_from_user.first_name,
+ last_name=event_from_user.last_name,
+ )
+
+ is_creator = event_from_user.id == config.bot.creator_id
+
+ if is_creator:
+ greeting = "👑 Создатель"
+ else:
+ greeting = "👨💼 Администратор"
+
+ content = f"""
+{greeting}
+
+📋 Панель управления+ +Выберите действие: +""" + + return {"content": content} + + +@inject +async def get_statistics_data( + users_repository: FromDishka[UsersRepository], + residents_repository: FromDishka[ResidentsRepository], + **kwargs, +): + all_users = await users_repository.get_all_users() + all_residents = await residents_repository.get_all_residents() + + total_users = len(all_users) + total_residents = len(all_residents) + busy_residents = len([r for r in all_residents if r.is_busy]) + total_active_hours = sum(r.active_hours for r in all_residents) + total_inactive_hours = sum(r.inactive_hours for r in all_residents) + admins_count = len([u for u in all_users if u.is_admin]) + + stats_text = f""" +
📊 Статистика системы+ +👥 Всего пользователей:
{total_users}
+👨💼 Администраторов: {admins_count}
+
+🏠 Всего резидентов: {total_residents}
+✅ Привязано к пользователям: {busy_residents}
+❌ Свободных: {total_residents - busy_residents}
+
+━━━━━━━━━━━━━━━━━━━━
+
+🟢 Всего отработанных часов: {total_inactive_hours} ч
+🔴 Всего неотработанных часов: {total_active_hours} ч
+📊 Общий итог: {total_active_hours + total_inactive_hours} ч
+"""
+
+ return {"stats_content": stats_text}
+
+
+async def on_rooms_click(callback, button, dialog_manager):
+ await callback.answer("⚠️ Функционал в разработке", show_alert=True)
+
+
+async def on_floors_click(callback, button, dialog_manager):
+ await callback.answer("⚠️ Функционал в разработке", show_alert=True)
+
+
+main_menu_window = Window(
+ Format("{content}"),
+ SwitchTo(
+ Const("🏠 Резиденты"),
+ id="residents_btn",
+ state=AdminMenuSG.residents,
+ ),
+ Row(
+ Button(
+ Const("🚪 Комнаты"),
+ id="rooms_btn",
+ on_click=on_rooms_click,
+ ),
+ Button(
+ Const("🏢 Этажи"),
+ id="floors_btn",
+ on_click=on_floors_click,
+ ),
+ ),
+ SwitchTo(
+ Const("📊 Статистика"),
+ id="stats_btn",
+ state=AdminMenuSG.statistics,
+ ),
+ SwitchTo(
+ Const("📢 Рассылка"),
+ id="broadcast_btn",
+ state=AdminMenuSG.broadcast,
+ ),
+ state=AdminMenuSG.main,
+ getter=get_admin_menu_data,
+)
+
+statistics_window = Window(
+ Format("{stats_content}"),
+ SwitchTo(Const("◀️ Назад"), id="back_from_stats", state=AdminMenuSG.main),
+ state=AdminMenuSG.statistics,
+ getter=get_statistics_data,
+)
diff --git a/src/dutylog/application/bot/user_dialogs/admin_dialogs/residents_management.py b/src/dutylog/application/bot/user_dialogs/admin_dialogs/residents_management.py
new file mode 100644
index 0000000..c9ec5f9
--- /dev/null
+++ b/src/dutylog/application/bot/user_dialogs/admin_dialogs/residents_management.py
@@ -0,0 +1,573 @@
+from aiogram.types import Message, CallbackQuery
+from aiogram_dialog import Window, DialogManager
+from aiogram_dialog.widgets.text import Format, Const
+from aiogram_dialog.widgets.kbd import Row, SwitchTo, Button, ScrollingGroup, Select, Group
+from aiogram_dialog.widgets.input import MessageInput
+from magic_filter import F
+from dishka import FromDishka
+from dishka.integrations.aiogram_dialog import inject
+
+from dutylog.application.bot.user_dialogs.states import AdminMenuSG
+from dutylog.infrastructure.database.repositories.residents_repository import (
+ ResidentsRepository,
+)
+from dutylog.infrastructure.database.repositories.rooms_repository import (
+ RoomsRepository,
+)
+from dutylog.infrastructure.database.repositories.floors_repository import (
+ FloorsRepository,
+)
+from dutylog.infrastructure.database.repositories.users_repository import (
+ UsersRepository,
+)
+
+
+@inject
+async def get_residents_list_data(
+ residents_repository: FromDishka[ResidentsRepository],
+ rooms_repository: FromDishka[RoomsRepository],
+ **kwargs,
+):
+ all_residents = await residents_repository.get_all_residents()
+
+ residents_with_rooms = []
+ for resident in all_residents:
+ room = await rooms_repository.get_room_by_id(resident.room)
+ room_number = room.number if room else 999999
+ residents_with_rooms.append((resident, room_number))
+
+ residents_with_rooms.sort(key=lambda x: x[1])
+
+ residents_data = []
+ for resident, room_number in residents_with_rooms:
+ status = "🟢" if resident.is_busy else "⚪️"
+ name = resident.real_name if resident.real_name else "Без имени"
+
+ residents_data.append(
+ (f"{name} | Комната {room_number} | {status}", resident.id)
+ )
+
+ content = f"""
+🏠 Резиденты+ +Всего резидентов:
{len(all_residents)}
+
+Выберите резидента для просмотра информации:
+"""
+
+ return {
+ "content": content,
+ "residents": residents_data,
+ }
+
+
+@inject
+async def get_resident_info_data(
+ dialog_manager: DialogManager,
+ residents_repository: FromDishka[ResidentsRepository],
+ rooms_repository: FromDishka[RoomsRepository],
+ users_repository: FromDishka[UsersRepository],
+ **kwargs,
+):
+ resident_id = dialog_manager.dialog_data.get("selected_resident_id")
+
+ if not resident_id:
+ return {"info_content": "Ошибка: резидент не выбран", "is_busy": False, "from_search": False}
+
+ resident = await residents_repository.get_resident_by_id(resident_id)
+
+ if not resident:
+ return {"info_content": "Ошибка: резидент не найден", "is_busy": False, "from_search": False}
+
+ room = await rooms_repository.get_room_by_id(resident.room)
+ room_number = room.number if room else "???"
+
+ name = resident.real_name if resident.real_name else "Без имени"
+ status = "🟢 Занят" if resident.is_busy else "⚪️ Свободен"
+
+ user_info = "Не привязан"
+ if resident.user_entity:
+ user = await users_repository.get_user_by_id(resident.user_entity)
+ if user:
+ if user.username:
+ username = f"@{user.username}"
+ else:
+ username = f"ID: {user.id}"
+ user_info = f"{user.first_name} ({username})"
+
+ info_content = f"""
+👤 Информация о резиденте+ +ID:
{resident.id}
+Имя: {name}
+Комната: {room_number}
+Статус: {status}
+Пользователь: {user_info}
+
+━━━━━━━━━━━━━━━━━━━━
+
+🟢 Отработанные часы: {resident.inactive_hours} ч
+🔴 Неотработанные часы: {resident.active_hours} ч
+"""
+
+ from_search = dialog_manager.dialog_data.get("from_search", False)
+
+ return {
+ "info_content": info_content,
+ "is_busy": resident.is_busy,
+ "from_search": from_search,
+ }
+
+
+async def on_resident_selected(
+ callback: CallbackQuery,
+ widget: Select,
+ dialog_manager: DialogManager,
+ item_id: str,
+):
+ dialog_manager.dialog_data["selected_resident_id"] = int(item_id)
+ dialog_manager.dialog_data["from_search"] = False
+ await dialog_manager.switch_to(AdminMenuSG.resident_info)
+
+
+async def on_add_resident(
+ callback: CallbackQuery,
+ button: Button,
+ dialog_manager: DialogManager,
+):
+ await dialog_manager.switch_to(AdminMenuSG.create_resident_name)
+
+
+async def on_filter_residents(
+ callback: CallbackQuery,
+ button: Button,
+ dialog_manager: DialogManager,
+):
+ await callback.answer("⚠️ Функционал в разработке", show_alert=True)
+
+
+async def on_search_residents(
+ callback: CallbackQuery,
+ button: Button,
+ dialog_manager: DialogManager,
+):
+ await dialog_manager.switch_to(AdminMenuSG.residents_search_input)
+
+
+@inject
+async def on_logout_resident_confirm(
+ callback: CallbackQuery,
+ button: Button,
+ dialog_manager: DialogManager,
+ residents_repository: FromDishka[ResidentsRepository],
+):
+ resident_id = dialog_manager.dialog_data.get("selected_resident_id")
+
+ if resident_id:
+ await residents_repository.unbind_user_from_resident(resident_id)
+
+ await dialog_manager.switch_to(AdminMenuSG.resident_info)
+
+
+async def on_logout_resident_cancel(
+ callback: CallbackQuery,
+ button: Button,
+ dialog_manager: DialogManager,
+):
+ await dialog_manager.switch_to(AdminMenuSG.resident_info)
+
+
+async def on_resident_name_input(
+ message: Message,
+ widget: MessageInput,
+ dialog_manager: DialogManager,
+):
+ if not message.text or len(message.text.strip()) < 2:
+ await message.answer("⚠️ Пожалуйста, введите корректное имя и фамилию")
+ return
+
+ dialog_manager.dialog_data["new_resident_name"] = message.text.strip()
+ await dialog_manager.switch_to(AdminMenuSG.create_resident_floor)
+
+
+@inject
+async def get_create_resident_floors_data(
+ floors_repository: FromDishka[FloorsRepository],
+ **kwargs,
+):
+ all_floors = await floors_repository.get_all_floors()
+ all_floors.sort(key=lambda f: f.number)
+
+ return {
+ "floors": [(f.id, f"Этаж {f.number}") for f in all_floors],
+ }
+
+
+async def on_create_resident_floor_selected(
+ callback: CallbackQuery,
+ widget: Select,
+ dialog_manager: DialogManager,
+ item_id: str,
+):
+ dialog_manager.dialog_data["new_resident_floor_id"] = int(item_id)
+ await dialog_manager.switch_to(AdminMenuSG.create_resident_room)
+
+
+@inject
+async def get_create_resident_rooms_data(
+ dialog_manager: DialogManager,
+ rooms_repository: FromDishka[RoomsRepository],
+ **kwargs,
+):
+ floor_id = dialog_manager.dialog_data.get("new_resident_floor_id")
+
+ if not floor_id:
+ return {"rooms": []}
+
+ rooms = await rooms_repository.get_rooms_by_floor(floor_id)
+ rooms.sort(key=lambda r: r.number)
+
+ return {
+ "rooms": [(r.id, str(r.number)) for r in rooms],
+ }
+
+
+async def on_create_resident_room_selected(
+ callback: CallbackQuery,
+ widget: Select,
+ dialog_manager: DialogManager,
+ item_id: str,
+):
+ dialog_manager.dialog_data["new_resident_room_id"] = int(item_id)
+ await dialog_manager.switch_to(AdminMenuSG.create_resident_confirm)
+
+
+async def get_create_resident_confirm_data(
+ dialog_manager: DialogManager,
+ **kwargs,
+):
+ name = dialog_manager.dialog_data.get("new_resident_name", "???")
+ return {"resident_name": name}
+
+
+@inject
+async def on_create_resident_confirm(
+ callback: CallbackQuery,
+ button: Button,
+ dialog_manager: DialogManager,
+ residents_repository: FromDishka[ResidentsRepository],
+):
+ name = dialog_manager.dialog_data.get("new_resident_name")
+ room_id = dialog_manager.dialog_data.get("new_resident_room_id")
+
+ if name and room_id:
+ await residents_repository.create_resident(
+ room_id=room_id,
+ real_name=name,
+ )
+ await callback.answer("✅ Резидент создан!")
+
+ await dialog_manager.switch_to(AdminMenuSG.residents)
+
+
+async def on_create_resident_cancel(
+ callback: CallbackQuery,
+ button: Button,
+ dialog_manager: DialogManager,
+):
+ await dialog_manager.switch_to(AdminMenuSG.residents)
+
+
+async def on_search_input(
+ message: Message,
+ widget: MessageInput,
+ dialog_manager: DialogManager,
+):
+ if not message.text or len(message.text.strip()) < 1:
+ await message.answer("⚠️ Пожалуйста, введите поисковый запрос")
+ return
+
+ query = message.text.strip()
+ if query.startswith("@"):
+ query = query[1:]
+
+ dialog_manager.dialog_data["search_query"] = query
+ dialog_manager.dialog_data["is_search_active"] = True
+ await dialog_manager.switch_to(AdminMenuSG.residents_search_results)
+
+
+@inject
+async def get_search_results_data(
+ dialog_manager: DialogManager,
+ residents_repository: FromDishka[ResidentsRepository],
+ rooms_repository: FromDishka[RoomsRepository],
+ users_repository: FromDishka[UsersRepository],
+ **kwargs,
+):
+ query = dialog_manager.dialog_data.get("search_query", "")
+
+ residents, search_type = await residents_repository.search_residents(
+ query, users_repository
+ )
+
+ if not residents:
+ return {
+ "content": f"""
+🔍 Результаты поиска+ +Запрос:
{query}
+
+❌ Ничего не найдено
+""",
+ "residents": [],
+ "has_results": False,
+ }
+
+ residents_with_rooms = []
+ for resident in residents:
+ room = await rooms_repository.get_room_by_id(resident.room)
+ room_number = room.number if room else 999999
+ residents_with_rooms.append((resident, room_number))
+
+ residents_with_rooms.sort(key=lambda x: x[1])
+
+ residents_data = []
+ for resident, room_number in residents_with_rooms:
+ status = "🟢" if resident.is_busy else "⚪️"
+ name = resident.real_name if resident.real_name else "Без имени"
+
+ residents_data.append(
+ (f"{name} | Комната {room_number} | {status}", resident.id)
+ )
+
+ content = f"""
+🔍 Результаты поиска по {search_type}+ +Найдено резидентов:
{len(residents)}
+
+Выберите резидента для просмотра информации:
+"""
+
+ return {
+ "content": content,
+ "residents": residents_data,
+ "has_results": True,
+ }
+
+
+async def on_search_resident_selected(
+ callback: CallbackQuery,
+ widget: Select,
+ dialog_manager: DialogManager,
+ item_id: str,
+):
+ dialog_manager.dialog_data["selected_resident_id"] = int(item_id)
+ dialog_manager.dialog_data["from_search"] = True
+ await dialog_manager.switch_to(AdminMenuSG.resident_info)
+
+
+residents_list_window = Window(
+ Format("{content}"),
+ Row(
+ Button(
+ Const("🔍 Поиск"),
+ id="search_residents_btn",
+ on_click=on_search_residents,
+ ),
+ Button(
+ Const("🔽 Фильтр"),
+ id="filter_residents_btn",
+ on_click=on_filter_residents,
+ ),
+ ),
+ ScrollingGroup(
+ Select(
+ Format("{item[0]}"),
+ id="residents_select",
+ item_id_getter=lambda x: x[1],
+ items="residents",
+ on_click=on_resident_selected,
+ ),
+ id="residents_scroll",
+ width=1,
+ height=7,
+ ),
+ Button(
+ Const("➕ Добавить резидента"),
+ id="add_resident_btn",
+ on_click=on_add_resident,
+ ),
+ SwitchTo(
+ Const("◀️ Назад"),
+ id="back_to_admin_menu",
+ state=AdminMenuSG.main,
+ ),
+ state=AdminMenuSG.residents,
+ getter=get_residents_list_data,
+)
+
+resident_info_window = Window(
+ Format("{info_content}"),
+ Row(
+ Button(
+ Const("Добавить часы"),
+ id="add_hours_btn",
+ on_click=lambda c, b, m: m.switch_to(AdminMenuSG.add_hours_select),
+ ),
+ Button(
+ Const("Отнять часы"),
+ id="remove_hours_btn",
+ on_click=lambda c, b, m: m.switch_to(AdminMenuSG.remove_hours_select),
+ ),
+ ),
+ Button(
+ Const("🚪 Разлогинить"),
+ id="logout_resident_btn",
+ on_click=lambda c, b, m: m.switch_to(AdminMenuSG.resident_logout_confirm),
+ when="is_busy",
+ ),
+ SwitchTo(
+ Const("◀️ Назад к результатам"),
+ id="back_to_search_results",
+ state=AdminMenuSG.residents_search_results,
+ when="from_search",
+ ),
+ SwitchTo(
+ Const("◀️ Назад к списку"),
+ id="back_to_residents_list",
+ state=AdminMenuSG.residents,
+ when=~F["from_search"],
+ ),
+ state=AdminMenuSG.resident_info,
+ getter=get_resident_info_data,
+)
+
+resident_logout_confirm_window = Window(
+ Const(
+ "⚠️ Подтверждение\n\nВы уверены, что хотите разлогинить этого резидента?" + ), + Row( + Button( + Const("✅ Да"), + id="confirm_logout", + on_click=on_logout_resident_confirm, + ), + Button( + Const("❌ Нет"), + id="cancel_logout", + on_click=on_logout_resident_cancel, + ), + ), + state=AdminMenuSG.resident_logout_confirm, +) + +create_resident_name_window = Window( + Const("
➕ Создание резидента\n\nВведите имя и фамилию резидента:"), + MessageInput(on_resident_name_input), + SwitchTo( + Const("◀️ Отмена"), + id="cancel_create_resident_name", + state=AdminMenuSG.residents, + ), + state=AdminMenuSG.create_resident_name, +) + +create_resident_floor_window = Window( + Const("
🏢 Выбор этажа\n\nВыберите этаж для нового резидента:"), + Group( + Select( + Format("{item[1]}"), + id="create_resident_floor_select", + item_id_getter=lambda x: x[0], + items="floors", + on_click=on_create_resident_floor_selected, + ), + width=2, + ), + SwitchTo( + Const("◀️ Назад"), + id="back_to_create_name", + state=AdminMenuSG.create_resident_name, + ), + state=AdminMenuSG.create_resident_floor, + getter=get_create_resident_floors_data, +) + +create_resident_room_window = Window( + Const("
🚪 Выбор комнаты\n\nВыберите комнату для нового резидента:"), + Group( + Select( + Format("{item[1]}"), + id="create_resident_room_select", + item_id_getter=lambda x: x[0], + items="rooms", + on_click=on_create_resident_room_selected, + ), + width=3, + ), + SwitchTo( + Const("◀️ Назад"), + id="back_to_create_floor", + state=AdminMenuSG.create_resident_floor, + ), + state=AdminMenuSG.create_resident_room, + getter=get_create_resident_rooms_data, +) + +create_resident_confirm_window = Window( + Format("
✅ Подтверждение\n\nСоздать резидента {resident_name}?"), + Row( + Button( + Const("✅ Да"), + id="confirm_create_resident", + on_click=on_create_resident_confirm, + ), + Button( + Const("❌ Нет"), + id="cancel_create_resident", + on_click=on_create_resident_cancel, + ), + ), + state=AdminMenuSG.create_resident_confirm, + getter=get_create_resident_confirm_data, +) + +search_input_window = Window( + Const("
🔍 Поиск резидентов\n\n
Введите номер комнаты, имя резидента или username пользователя. Можно указать только часть имени или username для поиска."), + MessageInput(on_search_input), + SwitchTo( + Const("◀️ Отмена"), + id="cancel_search", + state=AdminMenuSG.residents, + ), + state=AdminMenuSG.residents_search_input, +) + +search_results_window = Window( + Format("{content}"), + ScrollingGroup( + Select( + Format("{item[0]}"), + id="search_residents_select", + item_id_getter=lambda x: x[1], + items="residents", + on_click=on_search_resident_selected, + ), + id="search_residents_scroll", + width=1, + height=7, + when="has_results", + ), + SwitchTo( + Const("🔍 Новый поиск"), + id="new_search", + state=AdminMenuSG.residents_search_input, + ), + SwitchTo( + Const("◀️ К списку резидентов"), + id="back_to_residents_from_search", + state=AdminMenuSG.residents, + ), + state=AdminMenuSG.residents_search_results, + getter=get_search_results_data, +) diff --git a/src/dutylog/application/bot/user_dialogs/main_menu_dialog.py b/src/dutylog/application/bot/user_dialogs/main_menu_dialog.py index 89c6f22..df960ca 100644 --- a/src/dutylog/application/bot/user_dialogs/main_menu_dialog.py +++ b/src/dutylog/application/bot/user_dialogs/main_menu_dialog.py @@ -1,196 +1,12 @@ -from aiogram.types import User -from aiogram_dialog import Dialog, Window, DialogManager -from aiogram_dialog.widgets.text import Format, Const -from aiogram_dialog.widgets.kbd import SwitchTo, Back -from dishka import FromDishka -from dishka.integrations.aiogram_dialog import inject +from aiogram_dialog import Dialog -from dutylog.application.bot.user_dialogs.states import MainMenuSG -from dutylog.infrastructure.database.repositories.users_repository import ( - UsersRepository, -) -from dutylog.infrastructure.database.repositories.residents_repository import ( - ResidentsRepository, -) -from dutylog.infrastructure.database.repositories.rooms_repository import ( - RoomsRepository, -) -from dutylog.infrastructure.database.repositories.hours_transactions_repository import ( - HoursTransactionsRepository, -) -from dutylog.infrastructure.utils.config import Config - - -@inject -async def get_main_menu_data( - event_from_user: User, - users_repository: FromDishka[UsersRepository], - residents_repository: FromDishka[ResidentsRepository], - rooms_repository: FromDishka[RoomsRepository], - config: FromDishka[Config], - **kwargs, -): - user = await users_repository.get_or_create_user( - user_id=event_from_user.id, - username=event_from_user.username, - first_name=event_from_user.first_name, - last_name=event_from_user.last_name, - ) - - is_creator = event_from_user.id == config.bot.creator_id - is_admin = user.is_admin - - if is_creator: - greeting = "👑 Создатель" - elif is_admin: - greeting = "👨💼 Администратор" - else: - resident = await residents_repository.get_resident_by_user_id( - event_from_user.id - ) - if resident: - room = await rooms_repository.get_room_by_id(resident.room) - room_number = room.number if room else "???" - real_name = ( - resident.real_name if resident.real_name else event_from_user.first_name - ) - greeting = ( - f"👋 Привет, {real_name}!\n🚪 Комната
{room_number}"
- )
- else:
- greeting = f"👋 Привет, {event_from_user.first_name}!"
-
- if not is_admin and not is_creator:
- resident = await residents_repository.get_resident_by_user_id(
- event_from_user.id
- )
-
- if not resident:
- content = f"""
-{greeting}
-
-⚠️ Профиль не найден- -Вы еще не привязаны к резиденту. -Обратитесь к администратору для регистрации. -""" - has_resident = False - else: - content = f""" -{greeting} - -⏰ Ваши часы дежурств - -
🟢 Отработанные часы:-""" - has_resident = True - else: - content = f""" -{greeting} - -{resident.inactive_hours}ч -━━━━━━━━━━━━━━━━ -🔴 Неотработанные часы:{resident.active_hours}ч
📋 Панель управления- -Добро пожаловать в систему учета дежурств! -""" - has_resident = False - - return { - "content": content, - "is_regular_user": not is_admin and not is_creator, - "has_resident": has_resident, - } - - -@inject -async def get_history_data( - event_from_user: User, - residents_repository: FromDishka[ResidentsRepository], - transactions_repository: FromDishka[HoursTransactionsRepository], - **kwargs, -): - resident = await residents_repository.get_resident_by_user_id(event_from_user.id) - - if not resident: - history_text = """ -
📜 История операций- -Профиль не найден -""" - else: - transactions = await transactions_repository.get_resident_history(resident.id) - transactions_sorted = sorted(transactions, key=lambda x: x.created_at) - last_10 = transactions_sorted[:10] - - if not last_10: - history_text = """ -
📜 История операций- -История операций пуста -""" - else: - history_lines = [] - for tx in last_10: - emoji = "+" if tx.transaction_type == "increase" else "-" - date_str = tx.created_at.strftime("%d.%m.%Y %H:%M") - history_lines.append( - f"{emoji}
{tx.amount} ч • {date_str}"
- )
-
- history_text = f"""
-📜 История операций- -{"".join(f"{line}\n" for line in history_lines)} -Показаны последние 10 операций -""" - - return {"history_content": history_text} +from dutylog.application.bot.user_dialogs.user_menu.main_menu import main_menu_window +from dutylog.application.bot.user_dialogs.user_menu.history import history_window +from dutylog.application.bot.user_dialogs.user_menu.faq import faq_window main_menu_dialog = Dialog( - Window( - Format("{content}"), - SwitchTo( - Const("📜 История"), - id="history_btn", - state=MainMenuSG.history, - when="has_resident", - ), - SwitchTo( - Const("❓ FAQ"), - id="faq_btn", - state=MainMenuSG.faq, - when="is_regular_user", - ), - state=MainMenuSG.main, - getter=get_main_menu_data, - ), - Window( - Format("{history_content}"), - Back(Const("◀️ Назад")), - state=MainMenuSG.history, - getter=get_history_data, - ), - Window( - Const("""
❓ Часто задаваемые вопросы- -Что это за система? -
Это система учета дежурств в общежитии. Здесь отображаются ваши отработанные и неотработанные часы дежурств.- -Что делать, если я зарегистрировался не под собой? -
⚠️ Перерегистрацию может выполнить только администратор. Обратитесь к администратору для исправления данных.- -Как начисляются часы? -
Часы начисляются и списываются администраторами системы. Все изменения отображаются в разделе "История".- -Что означают активные и неактивные часы? -
🟢 Отработанные часы - часы, которые вы уже отработали -🔴 Неотработанные часы - часы, которые вам еще предстоит отработать- -Как связаться с администратором? -
Обратитесь к старосте вашего этажа или в администрацию общежития."""), - SwitchTo(Const("◀️ Назад"), id="back_to_main", state=MainMenuSG.main), - state=MainMenuSG.faq, - ), + main_menu_window, + history_window, + faq_window, ) diff --git a/src/dutylog/application/bot/user_dialogs/user_menu/__init__.py b/src/dutylog/application/bot/user_dialogs/user_menu/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/dutylog/application/bot/user_dialogs/user_menu/faq.py b/src/dutylog/application/bot/user_dialogs/user_menu/faq.py new file mode 100644 index 0000000..fdab72b --- /dev/null +++ b/src/dutylog/application/bot/user_dialogs/user_menu/faq.py @@ -0,0 +1,28 @@ +from aiogram_dialog import Window +from aiogram_dialog.widgets.text import Const +from aiogram_dialog.widgets.kbd import SwitchTo + +from dutylog.application.bot.user_dialogs.states import MainMenuSG + + +faq_window = Window( + Const("""
❓ Часто задаваемые вопросы+ +Что это за система? +
Это система учета дежурств в общежитии. Здесь отображаются ваши отработанные и неотработанные часы дежурств.+ +Что делать, если я зарегистрировался не под собой? +
⚠️ Перерегистрацию может выполнить только администратор. Обратитесь к администратору для исправления данных.+ +Как начисляются часы? +
Часы начисляются и списываются администраторами системы. Все изменения отображаются в разделе "История".+ +Что означают активные и неактивные часы? +
🟢 Отработанные часы - часы, которые вы уже отработали +🔴 Неотработанные часы - часы, которые вам еще предстоит отработать+ +Как связаться с администратором? +
Обратитесь к старосте вашего этажа или в администрацию общежития."""), + SwitchTo(Const("◀️ Назад"), id="back_to_main", state=MainMenuSG.main), + state=MainMenuSG.faq, +) diff --git a/src/dutylog/application/bot/user_dialogs/user_menu/history.py b/src/dutylog/application/bot/user_dialogs/user_menu/history.py new file mode 100644 index 0000000..248ac7d --- /dev/null +++ b/src/dutylog/application/bot/user_dialogs/user_menu/history.py @@ -0,0 +1,68 @@ +from aiogram.types import User +from aiogram_dialog import Window +from aiogram_dialog.widgets.text import Format +from aiogram_dialog.widgets.kbd import Back +from aiogram_dialog.widgets.text import Const +from dishka import FromDishka +from dishka.integrations.aiogram_dialog import inject + +from dutylog.application.bot.user_dialogs.states import MainMenuSG +from dutylog.infrastructure.database.repositories.residents_repository import ( + ResidentsRepository, +) +from dutylog.infrastructure.database.repositories.hours_transactions_repository import ( + HoursTransactionsRepository, +) + + +@inject +async def get_history_data( + event_from_user: User, + residents_repository: FromDishka[ResidentsRepository], + transactions_repository: FromDishka[HoursTransactionsRepository], + **kwargs, +): + resident = await residents_repository.get_resident_by_user_id(event_from_user.id) + + if not resident: + history_text = """ +
📜 История операций+ +Профиль не найден +""" + else: + transactions = await transactions_repository.get_resident_history(resident.id) + transactions_sorted = sorted(transactions, key=lambda x: x.created_at) + last_10 = transactions_sorted[:10] + + if not last_10: + history_text = """ +
📜 История операций+ +История операций пуста +""" + else: + history_lines = [] + for tx in last_10: + emoji = "+" if tx.transaction_type == "increase" else "-" + date_str = tx.created_at.strftime("%d.%m.%Y %H:%M") + history_lines.append( + f"{emoji}
{tx.amount} ч • {date_str}"
+ )
+
+ history_text = f"""
+📜 История операций+ +{"".join(f"{line}\n" for line in history_lines)} +Показаны последние 10 операций +""" + + return {"history_content": history_text} + + +history_window = Window( + Format("{history_content}"), + Back(Const("◀️ Назад")), + state=MainMenuSG.history, + getter=get_history_data, +) diff --git a/src/dutylog/application/bot/user_dialogs/user_menu/main_menu.py b/src/dutylog/application/bot/user_dialogs/user_menu/main_menu.py new file mode 100644 index 0000000..233e9f0 --- /dev/null +++ b/src/dutylog/application/bot/user_dialogs/user_menu/main_menu.py @@ -0,0 +1,119 @@ +from aiogram.types import User +from aiogram_dialog import Window, DialogManager +from aiogram_dialog.widgets.text import Format, Const +from aiogram_dialog.widgets.kbd import SwitchTo +from dishka import FromDishka +from dishka.integrations.aiogram_dialog import inject + +from dutylog.application.bot.user_dialogs.states import MainMenuSG +from dutylog.infrastructure.database.repositories.users_repository import ( + UsersRepository, +) +from dutylog.infrastructure.database.repositories.residents_repository import ( + ResidentsRepository, +) +from dutylog.infrastructure.database.repositories.rooms_repository import ( + RoomsRepository, +) +from dutylog.infrastructure.utils.config import Config + + +@inject +async def get_main_menu_data( + event_from_user: User, + users_repository: FromDishka[UsersRepository], + residents_repository: FromDishka[ResidentsRepository], + rooms_repository: FromDishka[RoomsRepository], + config: FromDishka[Config], + **kwargs, +): + user = await users_repository.get_or_create_user( + user_id=event_from_user.id, + username=event_from_user.username, + first_name=event_from_user.first_name, + last_name=event_from_user.last_name, + ) + + is_creator = event_from_user.id == config.bot.creator_id + is_admin = user.is_admin + + if is_creator: + greeting = "👑 Создатель" + elif is_admin: + greeting = "👨💼 Администратор" + else: + resident = await residents_repository.get_resident_by_user_id( + event_from_user.id + ) + if resident: + room = await rooms_repository.get_room_by_id(resident.room) + room_number = room.number if room else "???" + real_name = ( + resident.real_name if resident.real_name else event_from_user.first_name + ) + greeting = ( + f"👋 Привет, {real_name}!\n🚪 Комната
{room_number}"
+ )
+ else:
+ greeting = f"👋 Привет, {event_from_user.first_name}!"
+
+ if not is_admin and not is_creator:
+ resident = await residents_repository.get_resident_by_user_id(
+ event_from_user.id
+ )
+
+ if not resident:
+ content = f"""
+{greeting}
+
+⚠️ Профиль не найден+ +Вы еще не привязаны к резиденту. +Обратитесь к администратору для регистрации. +""" + has_resident = False + else: + content = f""" +{greeting} + +⏰ Ваши часы дежурств + +
🟢 Отработанные часы:+""" + has_resident = True + else: + content = f""" +{greeting} + +{resident.inactive_hours}ч +━━━━━━━━━━━━━━━━ +🔴 Неотработанные часы:{resident.active_hours}ч
📋 Панель управления+ +Добро пожаловать в систему учета дежурств! +""" + has_resident = False + + return { + "content": content, + "is_regular_user": not is_admin and not is_creator, + "has_resident": has_resident, + } + + +main_menu_window = Window( + Format("{content}"), + SwitchTo( + Const("📜 История"), + id="history_btn", + state=MainMenuSG.history, + when="has_resident", + ), + SwitchTo( + Const("❓ FAQ"), + id="faq_btn", + state=MainMenuSG.faq, + when="is_regular_user", + ), + state=MainMenuSG.main, + getter=get_main_menu_data, +)