Stage 06.2 - system settings navigation and product information screens

This commit is contained in:
2026-04-27 20:09:47 +03:00
parent f6fc300e84
commit cea74da4c4
9 changed files with 329 additions and 25 deletions

View File

@@ -1,2 +1,4 @@
# app/src/core/constants.py
APP_NAME = "Dzentra Bot" APP_NAME = "Dzentra Bot"
APP_VERSION = "2.0.0" APP_VERSION = "2.0.0"

View File

@@ -183,8 +183,8 @@ def build_system_text(*, include_updated_at: bool = False) -> str:
text = ( text = (
"<b>🖥️ Система</b>\n" "<b>🖥️ Система</b>\n"
f"🔸 <b>{snapshot.mode_label}</b>\n" f"🔸 <b>{snapshot.mode_label}</b>\n\n"
f"⏱️ {snapshot.timezone_name}\n\n" # f"⏱️ {snapshot.timezone_name}\n\n"
f"{components_block}" f"{components_block}"
) )

View File

@@ -1,8 +1,11 @@
# app/src/telegram/handlers/auto.py # app/src/telegram/handlers/auto.py
from __future__ import annotations
from aiogram import F, Router from aiogram import F, Router
from aiogram.fsm.context import FSMContext 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 from src.telegram.menus import AUTO_TEXT
@@ -10,8 +13,37 @@ from src.telegram.menus import AUTO_TEXT
router = Router(name="auto") 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: async def open_auto(message: Message, state: FSMContext) -> None:
# Глобальный экран: всегда выходим из текущего FSM-сценария.
await state.clear() await state.clear()
await message.answer(AUTO_TEXT) 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()

View File

@@ -8,6 +8,8 @@ from aiogram.types import CallbackQuery, InlineKeyboardMarkup, Message
from aiogram.utils.keyboard import InlineKeyboardBuilder from aiogram.utils.keyboard import InlineKeyboardBuilder
from src.core.system_status import build_system_text, get_system_snapshot, has_system_alerts 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 from src.trading.journal.service import JournalService
@@ -161,6 +163,77 @@ async def open_system_management(callback: CallbackQuery) -> None:
await callback.answer() 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 = (
"<b>🤖 Автоторговля</b>\n\n"
"<b>СИСТЕМА</b> · Настройки\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 = (
"<b>📊 Торговля</b>\n\n"
"<b>СИСТЕМА</b> · Настройки\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 = (
"<b>🌍 Общие</b>\n\n"
"<b>СИСТЕМА</b> · Настройки\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") @router.callback_query(F.data == "settings:journal")
async def open_journal_settings(callback: CallbackQuery) -> None: async def open_journal_settings(callback: CallbackQuery) -> None:
if callback.message is 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_archive")
builder.button(text="📦 Лимит", callback_data="settings:journal_limit") builder.button(text="📦 Лимит", callback_data="settings:journal_limit")
builder.button(text="⏳ Хранение", callback_data="settings:journal_retention") 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.button(text="📒 Журнал", callback_data="journal:1")
builder.adjust(2, 2, 2) builder.adjust(2, 2, 2)
@@ -195,6 +268,88 @@ async def open_journal_settings(callback: CallbackQuery) -> None:
await callback.answer() 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 = (
"<b>🗄 Архив</b>\n\n"
"<b>СИСТЕМА</b> · Настройки · Журнал\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 = (
"<b>📦 Лимит</b>\n\n"
"<b>СИСТЕМА</b> · Настройки · Журнал\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 = (
"<b>⏳ Хранение</b>\n\n"
"<b>СИСТЕМА</b> · Настройки · Журнал\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") @router.callback_query(F.data == "system:back")
async def back_to_system(callback: CallbackQuery) -> None: async def back_to_system(callback: CallbackQuery) -> None:
if callback.message is 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") @router.callback_query(F.data == "system:about")
async def open_system_about(callback: CallbackQuery) -> None: async def open_system_about(callback: CallbackQuery) -> None:
await callback.answer("О продукте скоро появится") 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 = (
"<b> Информация</b>\n\n"
"<b>СИСТЕМА</b>\n\n"
f"<b>{APP_NAME}</b>\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()

View File

@@ -33,7 +33,7 @@ def _trade_home_keyboard() -> InlineKeyboardMarkup:
builder.button(text="📝 Ордер", callback_data="trade:new_order") builder.button(text="📝 Ордер", callback_data="trade:new_order")
builder.button(text="📂 Ордера", callback_data="trade:orders") builder.button(text="📂 Ордера", callback_data="trade:orders")
builder.button(text="📜 История", callback_data="trade:history") 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) builder.adjust(2, 2)
return builder.as_markup() return builder.as_markup()

View File

@@ -1,14 +0,0 @@
# 0014 — System Navigation and Journal Settings
## Решение
Вынести журнал и системные настройки в отдельный раздел “Система” с многоуровневой навигацией.
## Причины
- разгрузить главное меню
- разделить operational UI и settings UI
- упростить масштабирование новых системных экранов
## Последствия
- появляется единый navigation pattern
- журнал становится отдельным subsystem
- проще расширять настройки

View File

@@ -0,0 +1,17 @@
# 0014 — System settings as centralized navigation hub
## Решение
Все настройки продукта централизуются через экран Система → Настройки.
## Причины
- единая точка входа
- масштабируемость
- понятная UX-структура
## Последствия
- все разделы получают связку экран ↔ настройки

View File

@@ -76,7 +76,9 @@
✔ system navigation redesign ✔ system navigation redesign
### 06.2 ### 06.2
⏳ archive / backup ✔ system settings navigation
✔ product information screen
✔ settings stubs
### 06.3 ### 06.3
⏳ retention / limits ⏳ retention / limits

View File

@@ -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"