mirror of
https://github.com/koloideal/DutyLog.git
synced 2026-06-10 02:15:30 +03:00
commit
This commit is contained in:
@@ -1,12 +1,16 @@
|
||||
from aiogram.types import User
|
||||
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 SwitchTo, Back, Start
|
||||
from aiogram_dialog.widgets.kbd import 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
|
||||
from dutylog.infrastructure.database.repositories.residents_repository import ResidentsRepository
|
||||
from dutylog.infrastructure.utils.config import Config
|
||||
|
||||
|
||||
@@ -42,50 +46,21 @@ async def get_admin_menu_data(
|
||||
return {"content": content}
|
||||
|
||||
|
||||
@inject
|
||||
async def get_users_list_data(
|
||||
users_repository: FromDishka[UsersRepository],
|
||||
**kwargs,
|
||||
):
|
||||
all_users = await users_repository.get_all_users()
|
||||
|
||||
if not all_users:
|
||||
users_text = """
|
||||
<blockquote>👥 <b>Список пользователей</b></blockquote>
|
||||
|
||||
<i>Пользователи не найдены</i>
|
||||
"""
|
||||
else:
|
||||
users_lines = []
|
||||
for user in all_users:
|
||||
name = user.first_name or user.username or f"ID: {user.id}"
|
||||
admin_badge = " 👨💼" if user.is_admin else ""
|
||||
users_lines.append(
|
||||
f"• <b>{name}</b>{admin_badge}\n"
|
||||
f" 🟢 <code>{user.active_hours}</code> ч | 🔴 <code>{user.inactive_hours}</code> ч"
|
||||
)
|
||||
|
||||
users_text = f"""
|
||||
<blockquote>👥 <b>Список пользователей</b></blockquote>
|
||||
|
||||
{"".join(f"{line}\n\n" for line in users_lines)}
|
||||
<i>Всего пользователей: {len(all_users)}</i>
|
||||
"""
|
||||
|
||||
return {"users_content": users_text}
|
||||
|
||||
|
||||
@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_active_hours = sum(user.active_hours for user in all_users)
|
||||
total_inactive_hours = sum(user.inactive_hours for user in all_users)
|
||||
admins_count = len([user for user in all_users if user.is_admin])
|
||||
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"""
|
||||
<blockquote>📊 <b>Статистика системы</b></blockquote>
|
||||
@@ -93,6 +68,10 @@ async def get_statistics_data(
|
||||
👥 <b>Всего пользователей:</b> <code>{total_users}</code>
|
||||
👨💼 <b>Администраторов:</b> <code>{admins_count}</code>
|
||||
|
||||
🏠 <b>Всего резидентов:</b> <code>{total_residents}</code>
|
||||
✅ <b>Привязано к пользователям:</b> <code>{busy_residents}</code>
|
||||
❌ <b>Свободных:</b> <code>{total_residents - busy_residents}</code>
|
||||
|
||||
━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
🟢 <b>Всего активных часов:</b> <code>{total_active_hours}</code> ч
|
||||
@@ -103,13 +82,87 @@ async def get_statistics_data(
|
||||
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
|
||||
except Exception:
|
||||
failed_count += 1
|
||||
|
||||
result_text = f"""
|
||||
<blockquote>📢 <b>Результаты рассылки</b></blockquote>
|
||||
|
||||
✅ <b>Успешно отправлено:</b> <code>{success_count}</code>
|
||||
❌ <b>Не удалось отправить:</b> <code>{failed_count}</code>
|
||||
📊 <b>Всего пользователей:</b> <code>{len(all_users) - 1}</code>
|
||||
"""
|
||||
|
||||
await callback.message.answer(result_text)
|
||||
await dialog_manager.switch_to(AdminMenuSG.main)
|
||||
|
||||
|
||||
async def on_broadcast_cancel(
|
||||
callback: CallbackQuery,
|
||||
button: Button,
|
||||
dialog_manager: DialogManager,
|
||||
):
|
||||
await dialog_manager.switch_to(AdminMenuSG.main)
|
||||
|
||||
|
||||
admin_menu_dialog = Dialog(
|
||||
Window(
|
||||
Format("{content}"),
|
||||
SwitchTo(
|
||||
Const("🏠 Резиденты"),
|
||||
id="residents_btn",
|
||||
state=AdminMenuSG.residents,
|
||||
),
|
||||
SwitchTo(
|
||||
Const("👥 Пользователи"),
|
||||
id="users_btn",
|
||||
state=AdminMenuSG.users_list,
|
||||
state=AdminMenuSG.users,
|
||||
),
|
||||
SwitchTo(
|
||||
Const("📊 Статистика"),
|
||||
@@ -125,20 +178,31 @@ admin_menu_dialog = Dialog(
|
||||
getter=get_admin_menu_data,
|
||||
),
|
||||
Window(
|
||||
Format("{users_content}"),
|
||||
Back(Const("◀️ Назад")),
|
||||
state=AdminMenuSG.users_list,
|
||||
getter=get_users_list_data,
|
||||
Const("<blockquote>🏠 <b>Резиденты</b></blockquote>\n\n<i>Функционал в разработке</i>"),
|
||||
SwitchTo(Const("◀️ Назад"), id="back_from_residents", state=AdminMenuSG.main),
|
||||
state=AdminMenuSG.residents,
|
||||
),
|
||||
Window(
|
||||
Const("<blockquote>👥 <b>Пользователи</b></blockquote>\n\n<i>Функционал в разработке</i>"),
|
||||
SwitchTo(Const("◀️ Назад"), id="back_from_users", state=AdminMenuSG.main),
|
||||
state=AdminMenuSG.users,
|
||||
),
|
||||
Window(
|
||||
Format("{stats_content}"),
|
||||
Back(Const("◀️ Назад")),
|
||||
SwitchTo(Const("◀️ Назад"), id="back_from_stats", state=AdminMenuSG.main),
|
||||
state=AdminMenuSG.statistics,
|
||||
getter=get_statistics_data,
|
||||
),
|
||||
Window(
|
||||
Const("<blockquote>📢 <b>Рассылка</b></blockquote>\n\n<i>Функционал в разработке</i>"),
|
||||
Back(Const("◀️ Назад")),
|
||||
Const("<blockquote>📢 <b>Рассылка</b></blockquote>\n\nОтправьте сообщение, которое хотите разослать всем пользователям:"),
|
||||
MessageInput(on_broadcast_message),
|
||||
SwitchTo(Const("◀️ Отмена"), id="cancel_broadcast", state=AdminMenuSG.main),
|
||||
state=AdminMenuSG.broadcast,
|
||||
),
|
||||
Window(
|
||||
Const("<blockquote>📢 <b>Подтверждение рассылки</b></blockquote>\n\n⚠️ Вы уверены, что хотите отправить это сообщение всем пользователям?"),
|
||||
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,
|
||||
),
|
||||
)
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
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, Start
|
||||
from aiogram_dialog.widgets.kbd import SwitchTo, Back
|
||||
from dishka import FromDishka
|
||||
from dishka.integrations.aiogram_dialog import inject
|
||||
|
||||
from dutylog.application.bot.user_dialogs.states import MainMenuSG, RegistrationSG
|
||||
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.hours_transactions_repository import HoursTransactionsRepository
|
||||
@@ -131,10 +131,10 @@ main_menu_dialog = Dialog(
|
||||
state=MainMenuSG.history,
|
||||
when="has_resident",
|
||||
),
|
||||
Start(
|
||||
Const("🔄 Перерегистрация"),
|
||||
id="reregister_btn",
|
||||
state=RegistrationSG.select_floor,
|
||||
SwitchTo(
|
||||
Const("❓ FAQ"),
|
||||
id="faq_btn",
|
||||
state=MainMenuSG.faq,
|
||||
when="is_regular_user",
|
||||
),
|
||||
state=MainMenuSG.main,
|
||||
@@ -146,5 +146,26 @@ main_menu_dialog = Dialog(
|
||||
state=MainMenuSG.history,
|
||||
getter=get_history_data,
|
||||
),
|
||||
Window(
|
||||
Const("""<blockquote>❓ <b>Часто задаваемые вопросы</b></blockquote>
|
||||
|
||||
<b>Что это за система?</b>
|
||||
<blockquote>Это система учета дежурств в общежитии. Здесь отображаются ваши отработанные и неотработанные часы дежурств.</blockquote>
|
||||
|
||||
<b>Что делать, если я зарегистрировался не под собой?</b>
|
||||
<blockquote>⚠️ Перерегистрацию может выполнить только администратор. Обратитесь к администратору для исправления данных.</blockquote>
|
||||
|
||||
<b>Как начисляются часы?</b>
|
||||
<blockquote>Часы начисляются и списываются администраторами системы. Все изменения отображаются в разделе "История".</blockquote>
|
||||
|
||||
<b>Что означают активные и неактивные часы?</b>
|
||||
<blockquote>🟢 <b>Отработанные часы</b> - часы, которые вы уже отработали
|
||||
🔴 <b>Неотработанные часы</b> - часы, которые вам еще предстоит отработать</blockquote>
|
||||
|
||||
<b>Как связаться с администратором?</b>
|
||||
<blockquote>Обратитесь к старосте вашего этажа или в администрацию общежития.</blockquote>"""),
|
||||
SwitchTo(Const("◀️ Назад"), id="back_to_main", state=MainMenuSG.main),
|
||||
state=MainMenuSG.faq,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ from aiogram.types import CallbackQuery
|
||||
from magic_filter import F
|
||||
from aiogram_dialog import Dialog, Window, DialogManager
|
||||
from aiogram_dialog.widgets.text import Format, Const
|
||||
from aiogram_dialog.widgets.kbd import Select, Cancel, Group
|
||||
from aiogram_dialog.widgets.kbd import Select, Group, SwitchTo
|
||||
from dishka import FromDishka
|
||||
from dishka.integrations.aiogram_dialog import inject
|
||||
|
||||
@@ -118,7 +118,7 @@ async def on_resident_selected(
|
||||
|
||||
registration_dialog = Dialog(
|
||||
Window(
|
||||
Const("<blockquote>🏢 <b>Выбор этажа</b></blockquote>\n\nВыберите этаж, на котором вы живете:", when="has_available"),
|
||||
Const("<blockquote>🏢 <b>Выбор этажа</b></blockquote>\n\n⚠️ <b>Внимание!</b> Перерегистрацию может выполнить только администратор. Выбирайте внимательно!\n\nВыберите этаж, на котором вы живете:", when="has_available"),
|
||||
Const("<blockquote>⚠️ <b>Нет доступных резидентов</b></blockquote>\n\nВсе резиденты уже заняты.\nОбратитесь к администратору.", when=~F["has_available"]),
|
||||
Group(
|
||||
Select(
|
||||
@@ -146,7 +146,7 @@ registration_dialog = Dialog(
|
||||
),
|
||||
width=3,
|
||||
),
|
||||
Cancel(Const("◀️ Назад")),
|
||||
SwitchTo(Const("◀️ Назад"), id="back_to_floors", state=RegistrationSG.select_floor),
|
||||
state=RegistrationSG.select_room,
|
||||
getter=get_rooms_data,
|
||||
),
|
||||
@@ -162,7 +162,7 @@ registration_dialog = Dialog(
|
||||
),
|
||||
width=1,
|
||||
),
|
||||
Cancel(Const("◀️ Назад")),
|
||||
SwitchTo(Const("◀️ Назад"), id="back_to_rooms", state=RegistrationSG.select_room),
|
||||
state=RegistrationSG.select_resident,
|
||||
getter=get_residents_data,
|
||||
),
|
||||
|
||||
@@ -4,13 +4,16 @@ from aiogram.fsm.state import State, StatesGroup
|
||||
class MainMenuSG(StatesGroup):
|
||||
main = State()
|
||||
history = State()
|
||||
faq = State()
|
||||
|
||||
|
||||
class AdminMenuSG(StatesGroup):
|
||||
main = State()
|
||||
users_list = State()
|
||||
residents = State()
|
||||
users = State()
|
||||
statistics = State()
|
||||
broadcast = State()
|
||||
broadcast_confirm = State()
|
||||
|
||||
|
||||
class RegistrationSG(StatesGroup):
|
||||
|
||||
Reference in New Issue
Block a user