Files
tgex-backend/telegram_bot/screens/my_projects.go
Artem Tsyrulnikov 9fbb6fdbbf улучшение ui
2026-01-09 12:23:02 +03:00

255 lines
7.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package screens
import (
"context"
"fmt"
"strings"
"example.com/m/bot"
"example.com/m/ui"
"github.com/NicoNex/echotron/v3"
"github.com/rs/zerolog/log"
)
const projectsPerPage = 6
type MyProjects struct {
CurrentPage int
WorkspaceID string
BackState bot.State
OpenPurchases bool
}
func (s *MyProjects) Enter(b *bot.Bot, mode bot.RenderMode) {
// JWT уже создан в Bot.Update(), просто берем из сессии
jwt := b.Session.JWT
if jwt == "" {
log.Error().Msg("JWT is empty in session")
b.SendNew("❌ Ошибка авторизации. Попробуйте /start login", Keyboard())
return
}
// Если workspace еще не выбран, получаем список workspace'ов
if s.WorkspaceID == "" {
workspaces, err := b.Backend.GetWorkspaces(context.Background(), jwt)
if err != nil {
log.Error().Err(err).Msg("Failed to get workspaces")
b.SendNew("❌ Не удалось загрузить workspace'ы", Keyboard())
return
}
if len(workspaces) == 0 {
// Автоматически создаем workspace с именем пользователя
username := "My Workspace"
chatInfo, err := b.GetChat(b.ChatID)
if err == nil && chatInfo.Result != nil {
if chatInfo.Result.Username != "" {
username = chatInfo.Result.Username
} else if chatInfo.Result.FirstName != "" {
username = chatInfo.Result.FirstName
if chatInfo.Result.LastName != "" {
username += " " + chatInfo.Result.LastName
}
}
}
log.Info().Str("username", username).Msg("Creating default workspace")
workspace, err := b.Backend.CreateWorkspace(context.Background(), jwt, username)
if err != nil {
log.Error().Err(err).Msg("Failed to create workspace")
b.SendNew("❌ Не удалось создать workspace", Keyboard())
return
}
s.WorkspaceID = workspace.ID
log.Info().Str("workspace_id", s.WorkspaceID).Str("name", workspace.Name).Msg("Workspace created successfully")
} else {
// Используем первый workspace по умолчанию
s.WorkspaceID = workspaces[0].ID
}
}
s.renderProjects(b, mode, jwt)
}
func (s *MyProjects) renderProjects(b *bot.Bot, mode bot.RenderMode, jwt string) {
page, err := b.Backend.GetProjects(context.Background(), jwt, s.WorkspaceID, s.CurrentPage+1, projectsPerPage)
if err != nil {
log.Error().Err(err).Msg("Failed to get projects")
b.SendNew("❌ Не удалось загрузить проекты", Keyboard())
return
}
var text string
var buttons [][]echotron.InlineKeyboardButton
navTitle := "Главное меню Мои проекты"
headerTitle := "Мои проекты"
emptyStateText := `У вас пока нет подключенных каналов.
<i>Что дает подключение канала:</i>
▸ Управление типом приглашений (публичные/с одобрением)
▸ Автоматическая отправка креативов в канал
▸ Интеграция с планом закупов
▸ Статистика и аналитика подписчиков
▸ Настройка уведомлений
Начните с добавления первого проекта`
nonEmptyText := "Управление вашими Telegram-каналами.\n\nВыберите канал"
if s.OpenPurchases {
navTitle = "Главное меню Закупы"
headerTitle = "Закупы"
nonEmptyText = "Выберите проект для просмотра закупов"
}
if len(page.Items) == 0 {
text = fmt.Sprintf(`<i>%s</i>
<b>%s</b>
%s`, navTitle, headerTitle, emptyStateText)
} else {
text = fmt.Sprintf(`<i>%s</i>
<b>%s</b>%s
%s`,
navTitle,
headerTitle,
ui.FormatPageInfo(s.CurrentPage, page.Pages),
nonEmptyText)
}
// Создаем слоты для проектов
var slots []echotron.InlineKeyboardButton
for _, project := range page.Items {
slots = append(slots, Button(project.Title, fmt.Sprintf("project:%s", project.ID)))
}
// Если страниц >= 2, заполняем до 6 слотов пустыми кнопками
if page.Pages >= 2 {
for len(slots) < projectsPerPage {
slots = append(slots, Button(" ", "empty"))
}
}
// Разбиваем слоты по 2 в ряду
for i := 0; i < len(slots); i += 2 {
var row []echotron.InlineKeyboardButton
row = append(row, slots[i])
// Второй слот в ряду (если есть)
if i+1 < len(slots) {
row = append(row, slots[i+1])
} else {
// Если нечетное количество, добавляем пустую кнопку
row = append(row, Button(" ", "empty"))
}
buttons = append(buttons, row)
}
// Ряд: Пагинация + Добавить (динамическая ширина)
if navRow := ui.BuildNavigationRow(ui.PaginationConfig{
CurrentPage: s.CurrentPage,
TotalPages: page.Pages,
MiddleButtons: []echotron.InlineKeyboardButton{Button(" Добавить", "add_project")},
}); navRow != nil {
buttons = append(buttons, navRow)
}
// Нижний ряд: Назад + Архив
buttons = append(buttons, Row(
Button("← Назад", "menu"),
Button("≡ Архив", "archive"),
))
keyboard := Keyboard(buttons...)
b.Render(text, keyboard, mode)
}
func (s *MyProjects) HandleCallback(b *bot.Bot, u *echotron.Update) {
if u.CallbackQuery == nil || u.CallbackQuery.Data == "" {
return
}
data := u.CallbackQuery.Data
switch {
case data == "prev":
if s.CurrentPage > 0 {
s.CurrentPage--
}
s.Enter(b, bot.EditMessage)
case data == "next":
s.CurrentPage++
s.Enter(b, bot.EditMessage)
case data == "menu", data == "main_menu":
b.SetState(&MainMenu{}, bot.EditMessage)
case data == "archive":
b.Edit("📦 Архив проектов\n\nЭта функция в разработке", Keyboard(
Row(Button("← Назад", "back_to_projects")),
))
case data == "add_project":
b.SetState(&AddProject{
BackState: &MyProjects{WorkspaceID: s.WorkspaceID},
}, bot.EditMessage)
case data == "back_to_projects":
s.Enter(b, bot.EditMessage)
case strings.HasPrefix(data, "project:"):
// Открытие деталей проекта
parts := strings.Split(data, ":")
if len(parts) == 2 {
projectID := parts[1]
// Получаем проект напрямую по ID
project, err := b.Backend.GetProject(
context.Background(),
s.WorkspaceID,
projectID,
)
if err != nil {
log.Error().Err(err).Str("project_id", projectID).Msg("Failed to get project")
return
}
if s.OpenPurchases {
b.SetState(&Purchases{
WorkspaceID: s.WorkspaceID,
ProjectID: project.ID,
ProjectTitle: project.Title,
BackState: &MyProjects{WorkspaceID: s.WorkspaceID, OpenPurchases: true},
}, bot.EditMessage)
} else {
b.SetState(&ProjectDetails{
Project: project,
WorkspaceID: s.WorkspaceID,
BackState: &MyProjects{WorkspaceID: s.WorkspaceID},
}, bot.EditMessage)
}
}
case strings.HasPrefix(data, "workspace:"):
// Позволяет переключаться между workspace'ами в будущем
parts := strings.Split(data, ":")
if len(parts) == 2 {
s.WorkspaceID = parts[1]
s.CurrentPage = 0
s.Enter(b, bot.EditMessage)
}
default:
s.Enter(b, bot.NewMessage)
}
return
}
func (s *MyProjects) HandleMessage(_ *bot.Bot, _ *echotron.Update) { return }
func (s *MyProjects) Handle(_ *bot.Bot, _ *echotron.Update) { return }