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_VERSION = "2.0.0"

View File

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

View File

@@ -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)
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 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 = (
"<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")
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 = (
"<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")
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("О продукте скоро появится")
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: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()

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
### 06.2
⏳ archive / backup
✔ system settings navigation
✔ product information screen
✔ settings stubs
### 06.3
⏳ 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"