From cea74da4c4e6049c66fe4a907653954f397e01eb Mon Sep 17 00:00:00 2001 From: Sergey Date: Mon, 27 Apr 2026 20:09:47 +0300 Subject: [PATCH] Stage 06.2 - system settings navigation and product information screens --- app/src/core/constants.py | 4 +- app/src/core/system_status.py | 4 +- app/src/telegram/handlers/auto.py | 40 +++- app/src/telegram/handlers/system.py | 194 +++++++++++++++++- app/src/telegram/handlers/trade/main.py | 2 +- ...-system-navigation-and-journal-settings.md | 14 -- .../0014-system-settings-navigation.md | 17 ++ docs/roadmap/master-roadmap.md | 4 +- ...6_2-system-settings-navigation-and-info.md | 75 +++++++ 9 files changed, 329 insertions(+), 25 deletions(-) delete mode 100644 docs/decisions/0014-system-navigation-and-journal-settings.md create mode 100644 docs/decisions/0014-system-settings-navigation.md create mode 100644 docs/stages/stage-06_2-system-settings-navigation-and-info.md diff --git a/app/src/core/constants.py b/app/src/core/constants.py index dadea16..c023bec 100644 --- a/app/src/core/constants.py +++ b/app/src/core/constants.py @@ -1,2 +1,4 @@ +# app/src/core/constants.py + APP_NAME = "Dzentra Bot" -APP_VERSION = "2.0.0" +APP_VERSION = "2.0.0" \ No newline at end of file diff --git a/app/src/core/system_status.py b/app/src/core/system_status.py index 82ac7ef..0fa6bed 100644 --- a/app/src/core/system_status.py +++ b/app/src/core/system_status.py @@ -183,8 +183,8 @@ def build_system_text(*, include_updated_at: bool = False) -> str: text = ( "🖥️ Система\n" - f"🔸 {snapshot.mode_label}\n" - f"⏱️ {snapshot.timezone_name}\n\n" + f"🔸 {snapshot.mode_label}\n\n" + # f"⏱️ {snapshot.timezone_name}\n\n" f"{components_block}" ) diff --git a/app/src/telegram/handlers/auto.py b/app/src/telegram/handlers/auto.py index 0708641..0d1e5d7 100644 --- a/app/src/telegram/handlers/auto.py +++ b/app/src/telegram/handlers/auto.py @@ -1,8 +1,11 @@ # app/src/telegram/handlers/auto.py +from __future__ import annotations + from aiogram import F, Router from aiogram.fsm.context import FSMContext -from aiogram.types import Message +from aiogram.types import CallbackQuery, InlineKeyboardMarkup, Message +from aiogram.utils.keyboard import InlineKeyboardBuilder from src.telegram.menus import AUTO_TEXT @@ -10,8 +13,37 @@ from src.telegram.menus import AUTO_TEXT router = Router(name="auto") -@router.message(F.text == "🤖 Авто") +def _auto_keyboard() -> InlineKeyboardMarkup: + builder = InlineKeyboardBuilder() + builder.button(text="🛠️ Настройки", callback_data="settings:auto") + builder.adjust(1) + return builder.as_markup() + + +async def _render_auto_screen( + target_message: Message, + *, + edit_mode: bool, +) -> None: + if edit_mode: + await target_message.edit_text(AUTO_TEXT, reply_markup=_auto_keyboard()) + else: + await target_message.answer(AUTO_TEXT, reply_markup=_auto_keyboard()) + + +@router.message(F.text.in_({"🤖 Автоторговля", "🤖 Авто"})) async def open_auto(message: Message, state: FSMContext) -> None: - # Глобальный экран: всегда выходим из текущего FSM-сценария. await state.clear() - await message.answer(AUTO_TEXT) \ No newline at end of file + await _render_auto_screen(message, edit_mode=False) + + +@router.callback_query(F.data == "auto:home") +async def open_auto_from_callback(callback: CallbackQuery, state: FSMContext) -> None: + await state.clear() + + if callback.message is None: + await callback.answer("Сообщение не найдено", show_alert=True) + return + + await _render_auto_screen(callback.message, edit_mode=True) + await callback.answer() \ No newline at end of file diff --git a/app/src/telegram/handlers/system.py b/app/src/telegram/handlers/system.py index 45b3d93..0a5a037 100644 --- a/app/src/telegram/handlers/system.py +++ b/app/src/telegram/handlers/system.py @@ -8,6 +8,8 @@ from aiogram.types import CallbackQuery, InlineKeyboardMarkup, Message from aiogram.utils.keyboard import InlineKeyboardBuilder from src.core.system_status import build_system_text, get_system_snapshot, has_system_alerts +from src.core.config import load_settings +from src.core.constants import APP_NAME, APP_VERSION from src.trading.journal.service import JournalService @@ -161,6 +163,77 @@ async def open_system_management(callback: CallbackQuery) -> None: await callback.answer() +@router.callback_query(F.data == "settings:auto") +async def open_auto_settings(callback: CallbackQuery) -> None: + if callback.message is None: + await callback.answer("Сообщение не найдено", show_alert=True) + return + + text = ( + "🤖 Автоторговля\n\n" + "СИСТЕМА · Настройки\n\n" + "Статус: —\n" + "Стратегия: —\n" + "Риск: —\n\n" + "В разработке." + ) + + builder = InlineKeyboardBuilder() + builder.button(text="⬅️ Назад", callback_data="system:management") + builder.button(text="🤖 Автоторговля", callback_data="auto:home") + builder.adjust(2) + + await callback.message.edit_text(text, reply_markup=builder.as_markup()) + await callback.answer() + + +@router.callback_query(F.data == "settings:trade") +async def open_trade_settings(callback: CallbackQuery) -> None: + if callback.message is None: + await callback.answer("Сообщение не найдено", show_alert=True) + return + + text = ( + "📊 Торговля\n\n" + "СИСТЕМА · Настройки\n\n" + "Инструмент: —\n" + "Тип ордера по умолчанию: —\n" + "Пресеты количества: —\n\n" + "В разработке." + ) + + builder = InlineKeyboardBuilder() + builder.button(text="⬅️ Назад", callback_data="system:management") + builder.button(text="📊 Торговля", callback_data="trade:home") + builder.adjust(2) + + await callback.message.edit_text(text, reply_markup=builder.as_markup()) + await callback.answer() + + +@router.callback_query(F.data == "settings:general") +async def open_general_settings(callback: CallbackQuery) -> None: + if callback.message is None: + await callback.answer("Сообщение не найдено", show_alert=True) + return + + text = ( + "🌍 Общие\n\n" + "СИСТЕМА · Настройки\n\n" + "Режим аккаунта: —\n" + "Часовой пояс: —\n" + "Язык интерфейса: ru\n\n" + "В разработке." + ) + + builder = InlineKeyboardBuilder() + builder.button(text="⬅️ Назад", callback_data="system:management") + builder.adjust(1) + + await callback.message.edit_text(text, reply_markup=builder.as_markup()) + await callback.answer() + + @router.callback_query(F.data == "settings:journal") async def open_journal_settings(callback: CallbackQuery) -> None: if callback.message is None: @@ -184,7 +257,7 @@ async def open_journal_settings(callback: CallbackQuery) -> None: builder.button(text="🗄 Архив", callback_data="settings:journal_archive") builder.button(text="📦 Лимит", callback_data="settings:journal_limit") builder.button(text="⏳ Хранение", callback_data="settings:journal_retention") - builder.button(text="⬅️ Назад", callback_data="system:control") + builder.button(text="⬅️ Назад", callback_data="system:management") builder.button(text="📒 Журнал", callback_data="journal:1") builder.adjust(2, 2, 2) @@ -195,6 +268,88 @@ async def open_journal_settings(callback: CallbackQuery) -> None: await callback.answer() +@router.callback_query(F.data == "settings:journal_archive") +async def open_journal_archive_settings(callback: CallbackQuery) -> None: + if callback.message is None: + await callback.answer("Сообщение не найдено", show_alert=True) + return + + text = ( + "🗄 Архив\n\n" + "СИСТЕМА · Настройки · Журнал\n\n" + "Автоматический архив журнала: —\n" + "Формат архива: —\n" + "Периодичность: —\n\n" + "В разработке." + ) + + builder = InlineKeyboardBuilder() + builder.button(text="⬅️ Назад", callback_data="settings:journal") + builder.button(text="📒 Журнал", callback_data="journal:1") + builder.adjust(2) + + await callback.message.edit_text(text, reply_markup=builder.as_markup()) + await callback.answer() + + +@router.callback_query(F.data == "settings:journal_limit") +async def open_journal_limit_settings(callback: CallbackQuery) -> None: + if callback.message is None: + await callback.answer("Сообщение не найдено", show_alert=True) + return + + text = ( + "📦 Лимит\n\n" + "СИСТЕМА · Настройки · Журнал\n\n" + "Текущий лимит: —\n\n" + "Доступные варианты:" + ) + + builder = InlineKeyboardBuilder() + builder.button(text="1 000", callback_data="settings:journal_limit_stub") + builder.button(text="5 000", callback_data="settings:journal_limit_stub") + builder.button(text="10 000", callback_data="settings:journal_limit_stub") + builder.button(text="∞", callback_data="settings:journal_limit_stub") + builder.button(text="⬅️ Назад", callback_data="settings:journal") + builder.adjust(2, 2, 1) + + await callback.message.edit_text(text, reply_markup=builder.as_markup()) + await callback.answer() + + +@router.callback_query(F.data == "settings:journal_retention") +async def open_journal_retention_settings(callback: CallbackQuery) -> None: + if callback.message is None: + await callback.answer("Сообщение не найдено", show_alert=True) + return + + text = ( + "⏳ Хранение\n\n" + "СИСТЕМА · Настройки · Журнал\n\n" + "Текущий срок хранения: —\n\n" + "Доступные варианты:" + ) + + builder = InlineKeyboardBuilder() + builder.button(text="7 дней", callback_data="settings:journal_retention_stub") + builder.button(text="30 дней", callback_data="settings:journal_retention_stub") + builder.button(text="90 дней", callback_data="settings:journal_retention_stub") + builder.button(text="∞", callback_data="settings:journal_retention_stub") + builder.button(text="⬅️ Назад", callback_data="settings:journal") + builder.adjust(2, 2, 1) + + await callback.message.edit_text(text, reply_markup=builder.as_markup()) + await callback.answer() + + +@router.callback_query(F.data.in_({ + "settings:journal_limit_stub", + "settings:journal_retention_stub", +})) +async def journal_settings_stub(callback: CallbackQuery) -> None: + await callback.answer("Настройка скоро появится", show_alert=True) + + @router.callback_query(F.data == "system:back") async def back_to_system(callback: CallbackQuery) -> None: if callback.message is None: @@ -216,4 +371,39 @@ async def back_to_system(callback: CallbackQuery) -> None: @router.callback_query(F.data == "system:about") async def open_system_about(callback: CallbackQuery) -> None: - await callback.answer("О продукте скоро появится") \ No newline at end of file + if callback.message is None: + await callback.answer("Сообщение не найдено", show_alert=True) + return + + settings = load_settings() + journal = JournalService() + + journal.log_ui_info( + event_type="system_about_opened", + message="Открыта информация о продукте.", + screen="system", + action="about", + user_id=callback.from_user.id if callback.from_user else None, + chat_id=callback.message.chat.id if callback.message.chat else None, + ) + + text = ( + "ℹ️ Информация\n\n" + "СИСТЕМА\n\n" + f"{APP_NAME}\n" + f"Версия: {APP_VERSION}\n" + f"Режим: {'DEMO' if 'demo' in settings.exchange_base_url.lower() else 'LIVE'}\n" + f"Часовой пояс: {settings.tz}\n\n" + "Торговый Telegram-бот для контроля рынка, портфеля, журнала событий " + "и будущей автоторговли." + ) + + builder = InlineKeyboardBuilder() + builder.button(text="⬅️ Назад", callback_data="system:back") + builder.adjust(1) + + await callback.message.edit_text( + text, + reply_markup=builder.as_markup(), + ) + await callback.answer() \ No newline at end of file diff --git a/app/src/telegram/handlers/trade/main.py b/app/src/telegram/handlers/trade/main.py index c05a897..eacb78c 100644 --- a/app/src/telegram/handlers/trade/main.py +++ b/app/src/telegram/handlers/trade/main.py @@ -33,7 +33,7 @@ def _trade_home_keyboard() -> InlineKeyboardMarkup: builder.button(text="📝 Ордер", callback_data="trade:new_order") builder.button(text="📂 Ордера", callback_data="trade:orders") builder.button(text="📜 История", callback_data="trade:history") - builder.button(text="⚙️ Настройки", callback_data="trade:settings") + builder.button(text="🛠️ Настройки", callback_data="settings:trade") builder.adjust(2, 2) return builder.as_markup() diff --git a/docs/decisions/0014-system-navigation-and-journal-settings.md b/docs/decisions/0014-system-navigation-and-journal-settings.md deleted file mode 100644 index 8041ad9..0000000 --- a/docs/decisions/0014-system-navigation-and-journal-settings.md +++ /dev/null @@ -1,14 +0,0 @@ -# 0014 — System Navigation and Journal Settings - -## Решение -Вынести журнал и системные настройки в отдельный раздел “Система” с многоуровневой навигацией. - -## Причины -- разгрузить главное меню -- разделить operational UI и settings UI -- упростить масштабирование новых системных экранов - -## Последствия -- появляется единый navigation pattern -- журнал становится отдельным subsystem -- проще расширять настройки \ No newline at end of file diff --git a/docs/decisions/0014-system-settings-navigation.md b/docs/decisions/0014-system-settings-navigation.md new file mode 100644 index 0000000..7dbe7fa --- /dev/null +++ b/docs/decisions/0014-system-settings-navigation.md @@ -0,0 +1,17 @@ +# 0014 — System settings as centralized navigation hub + +## Решение + +Все настройки продукта централизуются через экран Система → Настройки. + +## Причины + +- единая точка входа + +- масштабируемость + +- понятная UX-структура + +## Последствия + +- все разделы получают связку экран ↔ настройки \ No newline at end of file diff --git a/docs/roadmap/master-roadmap.md b/docs/roadmap/master-roadmap.md index 46d12c4..309b727 100644 --- a/docs/roadmap/master-roadmap.md +++ b/docs/roadmap/master-roadmap.md @@ -76,7 +76,9 @@ ✔ system navigation redesign ### 06.2 -⏳ archive / backup +✔ system settings navigation +✔ product information screen +✔ settings stubs ### 06.3 ⏳ retention / limits diff --git a/docs/stages/stage-06_2-system-settings-navigation-and-info.md b/docs/stages/stage-06_2-system-settings-navigation-and-info.md new file mode 100644 index 0000000..d04211d --- /dev/null +++ b/docs/stages/stage-06_2-system-settings-navigation-and-info.md @@ -0,0 +1,75 @@ +# Stage 06.2 — System Settings Navigation and Product Information + +## Что сделано + +### 1. Экран ℹ️ Информация +Добавлен полноценный экран информации о продукте. + +Показывает: +- название продукта +- версия +- режим работы (DEMO / LIVE) +- timezone +- описание продукта + +--- + +### 2. Новый navigation pattern + +Добавлена двусторонняя навигация: + +СИСТЕМА ↔ НАСТРОЙКИ ↔ ЭКРАНЫ + +Реализовано для: + +- 🤖 Автоторговля +- 📊 Торговля +- 📒 Журнал + +--- + +### 3. Настройки Журнала + +Добавлены отдельные экраны: + +- 🗑 Очистка +- 🗄 Архив +- 📦 Лимит +- ⏳ Хранение + +--- + +### 4. Заглушки настроек + +Созданы шаблонные экраны: + +#### 🤖 Автоторговля +- статус +- стратегия +- риск + +#### 📊 Торговля +- инструмент +- тип ордера +- пресеты количества + +#### 🌍 Общие +- режим +- timezone +- язык + +--- + +### 5. Обновление UI + +Убран timezone с главного экрана Система. + +Экран стал компактнее. + +--- + +## Commit + +```bash +git add . +git commit -m "Stage 06.2 - system settings navigation and product information screens" \ No newline at end of file