package screens import ( "context" "fmt" "strings" "example.com/m/bot" "github.com/NicoNex/echotron/v3" "github.com/rs/zerolog/log" ) const projectsPerPage = 6 type MyProjects struct { CurrentPage int WorkspaceID string BackState bot.State } func (s *MyProjects) Enter(b *bot.Bot, mode bot.RenderMode) { // Получаем или запрашиваем JWT jwt, err := s.ensureJWT(b) if err != nil { log.Error().Err(err).Msg("Failed to get JWT") b.SendNew("❌ Ошибка авторизации. Попробуйте /start login", bot.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'ы", bot.Keyboard()) return } if len(workspaces) == 0 { b.SendNew("У вас пока нет workspace'ов", bot.Keyboard()) return } // Используем первый 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("❌ Не удалось загрузить проекты", bot.Keyboard()) return } var text string var buttons [][]echotron.InlineKeyboardButton if len(page.Items) == 0 { text = `Мои проекты У вас пока нет подключенных каналов. Что дает подключение канала: ▸ Управление типом приглашений (публичные/с одобрением) ▸ Автоматическая отправка креативов в канал ▸ Интеграция с планом закупов ▸ Статистика и аналитика подписчиков ▸ Настройка уведомлений Начните с добавления первого проекта ↓` } else { pageInfo := "" if page.Pages > 1 { pageInfo = fmt.Sprintf("\nСтраница %d из %d", page.Page, page.Pages) } text = fmt.Sprintf(`Мои проекты Управление вашими Telegram-каналами.%s Выберите канал 👇 `, pageInfo) } // Создаем слоты для проектов var slots []echotron.InlineKeyboardButton for _, project := range page.Items { slots = append(slots, bot.Button(project.Title, fmt.Sprintf("project:%s", project.ID))) } // Если страниц >= 2, заполняем до 6 слотов пустыми кнопками if page.Pages >= 2 { for len(slots) < projectsPerPage { slots = append(slots, bot.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, bot.Button(" ", "empty")) } buttons = append(buttons, row) } // Ряд: Пагинация + Добавить var navRow []echotron.InlineKeyboardButton // Левая стрелка (или пустая кнопка) if s.CurrentPage > 0 { navRow = append(navRow, bot.Button("←", "prev")) } else { navRow = append(navRow, bot.Button(" ", "empty")) } // Кнопка "Добавить проект" всегда в центре navRow = append(navRow, bot.Button("+ Добавить", "add_project")) // Правая стрелка (или пустая кнопка) if s.CurrentPage+1 < page.Pages { navRow = append(navRow, bot.Button("→", "next")) } else { navRow = append(navRow, bot.Button(" ", "empty")) } buttons = append(buttons, navRow) // Нижний ряд: Назад + Архив buttons = append(buttons, bot.Row( bot.Button("← Назад", "menu"), bot.Button("≡ Архив", "archive"), )) keyboard := bot.Keyboard(buttons...) b.Render(text, keyboard, mode) } func (s *MyProjects) ensureJWT(b *bot.Bot) (string, error) { // Проверяем, есть ли JWT в сессии if b.Session.JWT != "" { return b.Session.JWT, nil } // Если нет, запрашиваем новый jwt, err := b.Backend.GetJWTByTelegramID(context.Background(), b.ChatID) if err != nil { return "", err } // Сохраняем в сессию b.Session.JWT = jwt return jwt, nil } 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 == "page_info", data == "empty": // Неактивные кнопки - ничего не делаем return case data == "archive": // TODO: реализовать экран архива b.Edit("📦 Архив проектов\n\nЭта функция в разработке", bot.Keyboard( bot.Row(bot.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 } 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 }