This commit is contained in:
2026-03-17 21:06:49 +03:00
parent eaeeb7bbce
commit b2eaa79f51
9 changed files with 85 additions and 162 deletions
-2
View File
@@ -11,7 +11,6 @@ from dishka.integrations.aiogram import setup_dishka
from dutylog.application.bot.user_handlers import router as user_router from dutylog.application.bot.user_handlers import router as user_router
from dutylog.application.bot.user_dialogs import main_menu_dialog from dutylog.application.bot.user_dialogs import main_menu_dialog
from dutylog.application.bot.admin_dialogs import admin_menu_dialog from dutylog.application.bot.admin_dialogs import admin_menu_dialog
from dutylog.application.bot.creator_dialogs import creator_menu_dialog
from dutylog.application.bot.user_dialogs.registration_dialog import registration_dialog from dutylog.application.bot.user_dialogs.registration_dialog import registration_dialog
from dutylog.infrastructure.ioc import ( from dutylog.infrastructure.ioc import (
ConfigProvider, ConfigProvider,
@@ -49,7 +48,6 @@ async def main():
dp.include_router(user_router) dp.include_router(user_router)
dp.include_router(main_menu_dialog) dp.include_router(main_menu_dialog)
dp.include_router(admin_menu_dialog) dp.include_router(admin_menu_dialog)
dp.include_router(creator_menu_dialog)
dp.include_router(registration_dialog) dp.include_router(registration_dialog)
setup_dialogs(dp) setup_dialogs(dp)
@@ -71,6 +71,16 @@ from dutylog.application.bot.admin_dialogs.faq import (
from dutylog.application.bot.admin_dialogs.top_residents import ( from dutylog.application.bot.admin_dialogs.top_residents import (
top_residents_window, top_residents_window,
) )
from dutylog.application.bot.creator_dialogs.admins_management import (
admins_list_window,
admin_info_window,
remove_admin_confirm_window,
add_admin_select_user_window,
add_admin_confirm_window,
)
from dutylog.application.bot.creator_dialogs.transactions_history import (
transactions_history_window,
)
admin_menu_dialog = Dialog( admin_menu_dialog = Dialog(
@@ -125,4 +135,10 @@ admin_menu_dialog = Dialog(
filter_hours_input_window, filter_hours_input_window,
filtered_results_window, filtered_results_window,
top_residents_window, top_residents_window,
admins_list_window,
admin_info_window,
remove_admin_confirm_window,
add_admin_select_user_window,
add_admin_confirm_window,
transactions_history_window,
) )
@@ -6,7 +6,7 @@ from dishka import FromDishka
from dishka.integrations.aiogram_dialog import inject from dishka.integrations.aiogram_dialog import inject
from datetime import datetime, timedelta from datetime import datetime, timedelta
from dutylog.application.bot.user_dialogs.states import AdminMenuSG, CreatorMenuSG from dutylog.application.bot.user_dialogs.states import AdminMenuSG
from dutylog.infrastructure.database.repositories.users_repository import ( from dutylog.infrastructure.database.repositories.users_repository import (
UsersRepository, UsersRepository,
) )
@@ -147,7 +147,7 @@ async def on_reporting_period_click(callback, button, dialog_manager):
async def on_admins_click(callback, button, dialog_manager): async def on_admins_click(callback, button, dialog_manager):
await dialog_manager.start(CreatorMenuSG.admins_list) await dialog_manager.switch_to(AdminMenuSG.admins_list)
main_menu_window = Window( main_menu_window = Window(
@@ -174,12 +174,6 @@ main_menu_window = Window(
id="reporting_period_btn", id="reporting_period_btn",
on_click=on_reporting_period_click, on_click=on_reporting_period_click,
), ),
Button(
Const("👨‍💼 Админы"),
id="admins_btn",
on_click=on_admins_click,
when="is_creator",
),
Row( Row(
SwitchTo( SwitchTo(
Const("📊 Статистика"), Const("📊 Статистика"),
@@ -192,15 +186,37 @@ main_menu_window = Window(
state=AdminMenuSG.top_residents, state=AdminMenuSG.top_residents,
), ),
), ),
Row(
SwitchTo(
Const("📢 Рассылка"),
id="broadcast_btn",
state=AdminMenuSG.broadcast,
),
SwitchTo( SwitchTo(
Const("❓ FAQ"), Const("❓ FAQ"),
id="faq_btn", id="faq_btn",
state=AdminMenuSG.faq, state=AdminMenuSG.faq,
), ),
),
Button(
Const("━━━━━━━━━━━━━━━━━━"),
id="separator_btn",
on_click=lambda c, b, m: c.answer(),
when="is_creator",
),
Row(
Button(
Const("👨‍💼 Админы"),
id="admins_btn",
on_click=on_admins_click,
when="is_creator",
),
SwitchTo( SwitchTo(
Const("📢 Рассылка"), Const("📜 История"),
id="broadcast_btn", id="history_btn",
state=AdminMenuSG.broadcast, state=AdminMenuSG.transactions_history,
when="is_creator",
),
), ),
state=AdminMenuSG.main, state=AdminMenuSG.main,
getter=get_admin_menu_data, getter=get_admin_menu_data,
@@ -1,8 +1,5 @@
from dutylog.application.bot.creator_dialogs.creator_menu_dialog import (
creator_menu_dialog,
)
from dutylog.application.bot.creator_dialogs.transactions_history import ( from dutylog.application.bot.creator_dialogs.transactions_history import (
transactions_history_window, transactions_history_window,
) )
__all__ = ["creator_menu_dialog", "transactions_history_window"] __all__ = ["transactions_history_window"]
@@ -5,7 +5,7 @@ from aiogram_dialog.widgets.kbd import SwitchTo, Button, Select, ScrollingGroup,
from dishka import FromDishka from dishka import FromDishka
from dishka.integrations.aiogram_dialog import inject from dishka.integrations.aiogram_dialog import inject
from dutylog.application.bot.user_dialogs.states import CreatorMenuSG, AdminMenuSG from dutylog.application.bot.user_dialogs.states import AdminMenuSG
from dutylog.infrastructure.database.repositories.users_repository import UsersRepository from dutylog.infrastructure.database.repositories.users_repository import UsersRepository
@@ -52,7 +52,7 @@ async def on_admin_selected(
item_id: str, item_id: str,
) -> None: ) -> None:
dialog_manager.dialog_data["selected_admin_id"] = int(item_id) dialog_manager.dialog_data["selected_admin_id"] = int(item_id)
await dialog_manager.switch_to(CreatorMenuSG.admin_info) await dialog_manager.switch_to(AdminMenuSG.admin_info)
async def on_add_admin_click( async def on_add_admin_click(
@@ -60,7 +60,7 @@ async def on_add_admin_click(
button: Button, button: Button,
dialog_manager: DialogManager, dialog_manager: DialogManager,
) -> None: ) -> None:
await dialog_manager.switch_to(CreatorMenuSG.add_admin_select_user) await dialog_manager.switch_to(AdminMenuSG.add_admin_select_user)
@inject @inject
@@ -101,7 +101,7 @@ async def on_remove_admin_click(
button: Button, button: Button,
dialog_manager: DialogManager, dialog_manager: DialogManager,
) -> None: ) -> None:
await dialog_manager.switch_to(CreatorMenuSG.remove_admin_confirm) await dialog_manager.switch_to(AdminMenuSG.remove_admin_confirm)
@inject @inject
@@ -148,7 +148,7 @@ async def on_remove_admin_confirm(
await users_repository.update_user(int(admin_id), is_admin=False) await users_repository.update_user(int(admin_id), is_admin=False)
await callback.answer("✅ Администратор удалён!") await callback.answer("✅ Администратор удалён!")
await dialog_manager.switch_to(CreatorMenuSG.admins_list) await dialog_manager.switch_to(AdminMenuSG.admins_list)
async def on_remove_admin_cancel( async def on_remove_admin_cancel(
@@ -156,7 +156,7 @@ async def on_remove_admin_cancel(
button: Button, button: Button,
dialog_manager: DialogManager, dialog_manager: DialogManager,
) -> None: ) -> None:
await dialog_manager.switch_to(CreatorMenuSG.admin_info) await dialog_manager.switch_to(AdminMenuSG.admin_info)
@inject @inject
@@ -204,7 +204,7 @@ async def on_user_selected(
item_id: str, item_id: str,
) -> None: ) -> None:
dialog_manager.dialog_data["selected_user_id"] = int(item_id) dialog_manager.dialog_data["selected_user_id"] = int(item_id)
await dialog_manager.switch_to(CreatorMenuSG.add_admin_confirm) await dialog_manager.switch_to(AdminMenuSG.add_admin_confirm)
@inject @inject
@@ -251,7 +251,7 @@ async def on_add_admin_confirm(
await users_repository.update_user(int(user_id), is_admin=True) await users_repository.update_user(int(user_id), is_admin=True)
await callback.answer("✅ Администратор добавлен!") await callback.answer("✅ Администратор добавлен!")
await dialog_manager.switch_to(CreatorMenuSG.admins_list) await dialog_manager.switch_to(AdminMenuSG.admins_list)
async def on_add_admin_cancel( async def on_add_admin_cancel(
@@ -259,7 +259,7 @@ async def on_add_admin_cancel(
button: Button, button: Button,
dialog_manager: DialogManager, dialog_manager: DialogManager,
) -> None: ) -> None:
await dialog_manager.switch_to(CreatorMenuSG.add_admin_select_user) await dialog_manager.switch_to(AdminMenuSG.add_admin_select_user)
async def on_back_to_main( async def on_back_to_main(
@@ -267,7 +267,7 @@ async def on_back_to_main(
button: Button, button: Button,
dialog_manager: DialogManager, dialog_manager: DialogManager,
) -> None: ) -> None:
await dialog_manager.done() await dialog_manager.switch_to(AdminMenuSG.main)
admins_list_window = Window( admins_list_window = Window(
@@ -289,32 +289,12 @@ admins_list_window = Window(
id="add_admin_btn", id="add_admin_btn",
on_click=on_add_admin_click, on_click=on_add_admin_click,
), ),
Button(
Const("──────────"),
id="separator_btn",
on_click=lambda c, b, m: None,
),
SwitchTo(
Const("👨‍💼 Админы"),
id="admins_btn",
state=CreatorMenuSG.admins_list,
),
SwitchTo(
Const("📜 История"),
id="history_btn",
state=CreatorMenuSG.transactions_history,
),
SwitchTo(
Const("🏆 Топ"),
id="top_btn",
state=CreatorMenuSG.top_residents,
),
Button( Button(
Const("◀️ Назад"), Const("◀️ Назад"),
id="back_to_main_from_admins", id="back_to_main_from_admins",
on_click=on_back_to_main, on_click=on_back_to_main,
), ),
state=CreatorMenuSG.admins_list, state=AdminMenuSG.admins_list,
getter=get_admins_list_data, getter=get_admins_list_data,
) )
@@ -328,9 +308,9 @@ admin_info_window = Window(
SwitchTo( SwitchTo(
Const("◀️ Назад"), Const("◀️ Назад"),
id="back_to_admins_list", id="back_to_admins_list",
state=CreatorMenuSG.admins_list, state=AdminMenuSG.admins_list,
), ),
state=CreatorMenuSG.admin_info, state=AdminMenuSG.admin_info,
getter=get_admin_info_data, getter=get_admin_info_data,
) )
@@ -348,7 +328,7 @@ remove_admin_confirm_window = Window(
on_click=on_remove_admin_cancel, on_click=on_remove_admin_cancel,
), ),
), ),
state=CreatorMenuSG.remove_admin_confirm, state=AdminMenuSG.remove_admin_confirm,
getter=get_remove_admin_confirm_data, getter=get_remove_admin_confirm_data,
) )
@@ -369,9 +349,9 @@ add_admin_select_user_window = Window(
SwitchTo( SwitchTo(
Const("◀️ Назад"), Const("◀️ Назад"),
id="back_to_admins_from_add", id="back_to_admins_from_add",
state=CreatorMenuSG.admins_list, state=AdminMenuSG.admins_list,
), ),
state=CreatorMenuSG.add_admin_select_user, state=AdminMenuSG.add_admin_select_user,
getter=get_add_admin_select_user_data, getter=get_add_admin_select_user_data,
) )
@@ -389,6 +369,6 @@ add_admin_confirm_window = Window(
on_click=on_add_admin_cancel, on_click=on_add_admin_cancel,
), ),
), ),
state=CreatorMenuSG.add_admin_confirm, state=AdminMenuSG.add_admin_confirm,
getter=get_add_admin_confirm_data, getter=get_add_admin_confirm_data,
) )
@@ -1,26 +0,0 @@
from aiogram_dialog import Dialog
from dutylog.application.bot.creator_dialogs.admins_management import (
admins_list_window,
admin_info_window,
remove_admin_confirm_window,
add_admin_select_user_window,
add_admin_confirm_window,
)
from dutylog.application.bot.creator_dialogs.transactions_history import (
transactions_history_window,
)
from dutylog.application.bot.creator_dialogs.top_residents import (
top_residents_window,
)
creator_menu_dialog = Dialog(
admins_list_window,
admin_info_window,
remove_admin_confirm_window,
add_admin_select_user_window,
add_admin_confirm_window,
transactions_history_window,
top_residents_window,
)
@@ -1,53 +0,0 @@
from aiogram_dialog import Window
from aiogram_dialog.widgets.text import Format, Const
from aiogram_dialog.widgets.kbd import Back
from dishka import FromDishka
from dishka.integrations.aiogram_dialog import inject
from dutylog.application.bot.user_dialogs.states import CreatorMenuSG
from dutylog.infrastructure.database.repositories.residents_repository import (
ResidentsRepository,
)
@inject
async def get_top_residents_data(
residents_repository: FromDishka[ResidentsRepository],
**kwargs,
) -> dict[str, str]:
all_residents = await residents_repository.get_all_residents()
residents_with_hours = [
r for r in all_residents
if (r.inactive_hours + r.active_hours) > 0
]
sorted_residents = sorted(
residents_with_hours,
key=lambda r: r.inactive_hours + r.active_hours,
reverse=True
)
top_residents = sorted_residents[:5]
if not top_residents:
content = "🏆 <b>Топ общежития</b>\n\n⚠️ Нет данных для отображения топа."
else:
content = "🏆 <b>Топ общежития</b>\n\n"
medals = ["🥇", "🥈", "🥉", "4.", "5."]
for idx, resident in enumerate(top_residents):
total_hours = resident.inactive_hours + resident.active_hours
name = resident.real_name or "Без имени"
content += f"<blockquote>{medals[idx]} <b>{name}</b> — <code>{total_hours}</code> ч</blockquote>\n"
return {"content": content}
top_residents_window = Window(
Format("{content}"),
Back(Const("◀️ Назад")),
state=CreatorMenuSG.top_residents,
getter=get_top_residents_data,
)
@@ -1,10 +1,10 @@
from aiogram_dialog import Window from aiogram_dialog import Window
from aiogram_dialog.widgets.text import Format, Const from aiogram_dialog.widgets.text import Format, Const
from aiogram_dialog.widgets.kbd import Back, NumberedPager, Group from aiogram_dialog.widgets.kbd import SwitchTo
from dishka import FromDishka from dishka import FromDishka
from dishka.integrations.aiogram_dialog import inject from dishka.integrations.aiogram_dialog import inject
from dutylog.application.bot.user_dialogs.states import CreatorMenuSG from dutylog.application.bot.user_dialogs.states import AdminMenuSG
from dutylog.infrastructure.database.repositories.hours_transactions_repository import ( from dutylog.infrastructure.database.repositories.hours_transactions_repository import (
HoursTransactionsRepository, HoursTransactionsRepository,
) )
@@ -22,25 +22,19 @@ async def get_transactions_history_data(
transactions_repository: FromDishka[HoursTransactionsRepository], transactions_repository: FromDishka[HoursTransactionsRepository],
residents_repository: FromDishka[ResidentsRepository], residents_repository: FromDishka[ResidentsRepository],
rooms_repository: FromDishka[RoomsRepository], rooms_repository: FromDishka[RoomsRepository],
dialog_manager,
**kwargs, **kwargs,
) -> dict: ) -> dict:
all_transactions = await transactions_repository.get_all_transactions() all_transactions = await transactions_repository.get_all_transactions()
all_transactions.sort(key=lambda t: t.created_at, reverse=True) all_transactions.sort(key=lambda t: t.created_at, reverse=True)
page = await dialog_manager.find("transactions_pager").get_page() recent_transactions = all_transactions[:8]
per_page = 7
start_idx = page * per_page
end_idx = start_idx + per_page
page_transactions = all_transactions[start_idx:end_idx] if not recent_transactions:
content = "<blockquote>📜 <b>История транзакций</b></blockquote>\n\n<i>Нет транзакций</i>"
if not page_transactions:
content = "<blockquote>📜 <b>История всех транзакций</b></blockquote>\n\n<i>Нет транзакций</i>"
else: else:
content = "<blockquote>📜 <b>История всех транзакций</b></blockquote>\n\n" content = "<blockquote>📜 <b>История транзакций</b></blockquote>\n\n<i>Последние 8 транзакций:</i>\n\n"
for tx in page_transactions: for tx in recent_transactions:
resident = await residents_repository.get_by_id(tx.resident_id) resident = await residents_repository.get_by_id(tx.resident_id)
if not resident: if not resident:
continue continue
@@ -59,21 +53,16 @@ async def get_transactions_history_data(
content += f"<blockquote><b>{operation}</b> {emoji}<code>{tx.amount}</code> ч{room_mark}\n👤 {resident.real_name or 'Без имени'} (к. {room_number})\n📅 {date_str}{remark_text}</blockquote>\n" content += f"<blockquote><b>{operation}</b> {emoji}<code>{tx.amount}</code> ч{room_mark}\n👤 {resident.real_name or 'Без имени'} (к. {room_number})\n📅 {date_str}{remark_text}</blockquote>\n"
total_pages = (len(all_transactions) + per_page - 1) // per_page return {"content": content}
return {
"content": content,
"pages": total_pages,
}
transactions_history_window = Window( transactions_history_window = Window(
Format("{content}"), Format("{content}"),
Group( SwitchTo(
NumberedPager(scroll="transactions_pager", when="pages"), Const("◀️ Назад"),
width=8, id="back_to_main",
state=AdminMenuSG.main,
), ),
Back(Const("◀️ Назад")), state=AdminMenuSG.transactions_history,
state=CreatorMenuSG.transactions_history,
getter=get_transactions_history_data, getter=get_transactions_history_data,
) )
@@ -60,6 +60,12 @@ class AdminMenuSG(StatesGroup):
broadcast = State() broadcast = State()
broadcast_confirm = State() broadcast_confirm = State()
top_residents = State() top_residents = State()
admins_list = State()
admin_info = State()
remove_admin_confirm = State()
add_admin_select_user = State()
add_admin_confirm = State()
transactions_history = State()
class CreatorMenuSG(StatesGroup): class CreatorMenuSG(StatesGroup):