обработка обновлений последовательно для каждого юзера благодаря mutex
This commit is contained in:
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"example.com/m/adapter/backend"
|
||||
"github.com/NicoNex/echotron/v3"
|
||||
@@ -37,6 +38,7 @@ type Exec struct {
|
||||
|
||||
type Bot struct {
|
||||
echotron.API
|
||||
mu sync.Mutex
|
||||
ChatID int64
|
||||
exec *Exec
|
||||
CurrentState State
|
||||
@@ -205,6 +207,9 @@ func (b *Bot) SendMessageWithURLButton(text, buttonText, url string) {
|
||||
}
|
||||
|
||||
func (b *Bot) Update(u *echotron.Update) {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
|
||||
log.Info().
|
||||
Int64("chat_id", b.ChatID).
|
||||
Str("current_state", fmt.Sprintf("%T", b.CurrentState)).
|
||||
|
||||
@@ -12,6 +12,11 @@ type AcceptWorkspaceInvite struct {
|
||||
InviteID string
|
||||
}
|
||||
|
||||
const msgAcceptWorkspaceInviteSuccessfully = `
|
||||
✅ Вы присоединились к рабочему пространству!
|
||||
Администратор сможет выдать вам необходимые права в веб-интерфейсе.
|
||||
`
|
||||
|
||||
func (s *AcceptWorkspaceInvite) Enter(b *bot.Bot, mode bot.RenderMode) {
|
||||
// Получаем JWT токен
|
||||
jwt, err := b.Backend.GetJWTByTelegramID(context.Background(), b.ChatID)
|
||||
@@ -29,11 +34,7 @@ func (s *AcceptWorkspaceInvite) Enter(b *bot.Bot, mode bot.RenderMode) {
|
||||
return
|
||||
}
|
||||
|
||||
// Успешно принято
|
||||
text := "✅ Вы присоединились к рабочему пространству!\n\n" +
|
||||
"Администратор сможет выдать вам необходимые права в веб-интерфейсе."
|
||||
|
||||
b.SendNew(text, bot.Keyboard(
|
||||
b.SendNew(msgAcceptWorkspaceInviteSuccessfully, bot.Keyboard(
|
||||
bot.Row(bot.Button("🏠 Главное меню", "menu")),
|
||||
))
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
|
||||
"example.com/m/bot"
|
||||
"example.com/m/ui"
|
||||
"github.com/NicoNex/echotron/v3"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
@@ -84,26 +85,31 @@ func (s *AddPurchase) renderSelection(b *bot.Bot, mode bot.RenderMode, jwt strin
|
||||
if len(page.Items) == 0 {
|
||||
text += "У вас нет проектов. Создайте проект и попробуйте снова.\n\n"
|
||||
} else {
|
||||
pageInfo := ""
|
||||
if page.Pages > 1 {
|
||||
pageInfo = fmt.Sprintf(" <i>(стр. %d/%d)</i>", page.Page, page.Pages)
|
||||
}
|
||||
text += fmt.Sprintf("Проекты%s\n\n", ui.FormatPageInfo(s.ProjectPage, page.Pages))
|
||||
|
||||
text += fmt.Sprintf("Проекты%s\n\n", pageInfo)
|
||||
var slots []echotron.InlineKeyboardButton
|
||||
for _, project := range page.Items {
|
||||
buttons = append(buttons, bot.Row(
|
||||
bot.Button(project.Title, fmt.Sprintf("project:%s", project.ID)),
|
||||
))
|
||||
slots = append(slots, bot.Button(project.Title, fmt.Sprintf("project:%s", project.ID)))
|
||||
}
|
||||
if page.Pages >= 2 {
|
||||
for len(slots) < addPurchaseProjectsPerPage {
|
||||
slots = append(slots, bot.Button(" ", "empty"))
|
||||
}
|
||||
}
|
||||
for i := 0; i < len(slots); i += 2 {
|
||||
row := []echotron.InlineKeyboardButton{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.ProjectPage > 0 {
|
||||
navRow = append(navRow, bot.Button("←", "prev_project"))
|
||||
}
|
||||
if s.ProjectPage+1 < page.Pages {
|
||||
navRow = append(navRow, bot.Button("→", "next_project"))
|
||||
}
|
||||
if len(navRow) > 0 {
|
||||
if navRow := ui.BuildNavigationRow(ui.PaginationConfig{
|
||||
CurrentPage: s.ProjectPage,
|
||||
TotalPages: page.Pages,
|
||||
}); navRow != nil {
|
||||
buttons = append(buttons, navRow)
|
||||
}
|
||||
}
|
||||
@@ -146,27 +152,31 @@ func (s *AddPurchase) renderSelection(b *bot.Bot, mode bot.RenderMode, jwt strin
|
||||
|
||||
Вернитесь назад и создайте креатив в разделе <b>Креативы</b>`
|
||||
} else {
|
||||
pageInfo := ""
|
||||
if page.Pages > 1 {
|
||||
pageInfo = fmt.Sprintf(" <i>(стр. %d/%d)</i>", page.Page, page.Pages)
|
||||
}
|
||||
|
||||
text += fmt.Sprintf("Креативы%s\n\n", pageInfo)
|
||||
text += fmt.Sprintf("Креативы%s\n\n", ui.FormatPageInfo(s.CreativePage, page.Pages))
|
||||
|
||||
var slots []echotron.InlineKeyboardButton
|
||||
for _, creative := range page.Items {
|
||||
buttons = append(buttons, bot.Row(
|
||||
bot.Button(creative.Name, fmt.Sprintf("creative:%s", creative.ID)),
|
||||
))
|
||||
slots = append(slots, bot.Button(creative.Name, fmt.Sprintf("creative:%s", creative.ID)))
|
||||
}
|
||||
if page.Pages >= 2 {
|
||||
for len(slots) < addPurchaseCreativesPerPage {
|
||||
slots = append(slots, bot.Button(" ", "empty"))
|
||||
}
|
||||
}
|
||||
for i := 0; i < len(slots); i += 2 {
|
||||
row := []echotron.InlineKeyboardButton{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.CreativePage > 0 {
|
||||
navRow = append(navRow, bot.Button("←", "prev_creative"))
|
||||
}
|
||||
if s.CreativePage+1 < page.Pages {
|
||||
navRow = append(navRow, bot.Button("→", "next_creative"))
|
||||
}
|
||||
if len(navRow) > 0 {
|
||||
if navRow := ui.BuildNavigationRow(ui.PaginationConfig{
|
||||
CurrentPage: s.CreativePage,
|
||||
TotalPages: page.Pages,
|
||||
}); navRow != nil {
|
||||
buttons = append(buttons, navRow)
|
||||
}
|
||||
}
|
||||
@@ -232,27 +242,33 @@ func (s *AddPurchase) HandleCallback(b *bot.Bot, u *echotron.Update) {
|
||||
s.ActivePicker = ""
|
||||
s.Enter(b, bot.EditMessage)
|
||||
|
||||
case "prev_project":
|
||||
case "prev":
|
||||
if s.ActivePicker == "project_list" {
|
||||
if s.ProjectPage > 0 {
|
||||
s.ProjectPage--
|
||||
}
|
||||
s.Enter(b, bot.EditMessage)
|
||||
|
||||
case "next_project":
|
||||
s.ProjectPage++
|
||||
s.Enter(b, bot.EditMessage)
|
||||
|
||||
case "prev_creative":
|
||||
return
|
||||
}
|
||||
if s.ActivePicker == "creative_list" {
|
||||
if s.CreativePage > 0 {
|
||||
s.CreativePage--
|
||||
}
|
||||
s.Enter(b, bot.EditMessage)
|
||||
|
||||
case "next_creative":
|
||||
s.CreativePage++
|
||||
s.Enter(b, bot.EditMessage)
|
||||
return
|
||||
}
|
||||
|
||||
case "next":
|
||||
if s.ActivePicker == "project_list" {
|
||||
s.ProjectPage++
|
||||
s.Enter(b, bot.EditMessage)
|
||||
return
|
||||
}
|
||||
if s.ActivePicker == "creative_list" {
|
||||
s.CreativePage++
|
||||
s.Enter(b, bot.EditMessage)
|
||||
return
|
||||
}
|
||||
if s.ProjectID != "" && s.CreativeID != "" {
|
||||
b.SetState(&SelectChannelsForPurchase{
|
||||
WorkspaceID: s.WorkspaceID,
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"example.com/m/bot"
|
||||
"example.com/m/ui"
|
||||
"github.com/NicoNex/echotron/v3"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
@@ -71,11 +72,6 @@ func (s *Creatives) renderCreatives(b *bot.Bot, mode bot.RenderMode, jwt string)
|
||||
|
||||
Начните с добавления первого креатива`, navPath)
|
||||
} else {
|
||||
pageInfo := ""
|
||||
if page.Pages > 1 {
|
||||
pageInfo = fmt.Sprintf(" <i>(стр. %d/%d)</i>", page.Page, page.Pages)
|
||||
}
|
||||
|
||||
text = fmt.Sprintf(`<i>%s</i>
|
||||
|
||||
<b>Креативы</b>%s
|
||||
@@ -84,7 +80,7 @@ func (s *Creatives) renderCreatives(b *bot.Bot, mode bot.RenderMode, jwt string)
|
||||
|
||||
Выберите креатив`,
|
||||
navPath,
|
||||
pageInfo)
|
||||
ui.FormatPageInfo(s.CurrentPage, page.Pages))
|
||||
}
|
||||
|
||||
// Создаем слоты для креативов
|
||||
@@ -117,22 +113,13 @@ func (s *Creatives) renderCreatives(b *bot.Bot, mode bot.RenderMode, jwt string)
|
||||
}
|
||||
|
||||
// Ряд: Пагинация + Добавить (динамическая ширина)
|
||||
var navRow []echotron.InlineKeyboardButton
|
||||
|
||||
// Левая стрелка (только если есть предыдущая страница)
|
||||
if s.CurrentPage > 0 {
|
||||
navRow = append(navRow, bot.Button("←", "prev"))
|
||||
}
|
||||
|
||||
// Кнопка "Добавить креатив" всегда в центре
|
||||
navRow = append(navRow, bot.Button("+ Добавить", "add_creative"))
|
||||
|
||||
// Правая стрелка (только если есть следующая страница)
|
||||
if s.CurrentPage+1 < page.Pages {
|
||||
navRow = append(navRow, bot.Button("→", "next"))
|
||||
}
|
||||
|
||||
if navRow := ui.BuildNavigationRow(ui.PaginationConfig{
|
||||
CurrentPage: s.CurrentPage,
|
||||
TotalPages: page.Pages,
|
||||
MiddleButtons: []echotron.InlineKeyboardButton{bot.Button("+ Добавить", "add_creative")},
|
||||
}); navRow != nil {
|
||||
buttons = append(buttons, navRow)
|
||||
}
|
||||
|
||||
// Нижний ряд: Назад + Архив
|
||||
buttons = append(buttons, bot.Row(
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"example.com/m/bot"
|
||||
"example.com/m/ui"
|
||||
"github.com/NicoNex/echotron/v3"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
@@ -75,63 +76,41 @@ func (s *MyPlacements) renderPlacements(b *bot.Bot, mode bot.RenderMode, jwt str
|
||||
|
||||
Размещения создаются автоматически при создании закупа`
|
||||
} else {
|
||||
pageInfo := ""
|
||||
if page.Pages > 1 {
|
||||
pageInfo = fmt.Sprintf(" <i>(стр. %d/%d)</i>", s.CurrentPage+1, page.Pages)
|
||||
}
|
||||
text += fmt.Sprintf("Выберите размещение для деталей%s\n\n", pageInfo)
|
||||
text += fmt.Sprintf("Выберите размещение для деталей%s\n\n", ui.FormatPageInfo(s.CurrentPage, page.Pages))
|
||||
|
||||
// Размещения по 2 в ряду
|
||||
for i := 0; i < len(page.Items); i += 2 {
|
||||
var row []echotron.InlineKeyboardButton
|
||||
|
||||
// Первый placement в ряду
|
||||
placement := page.Items[i]
|
||||
var slots []echotron.InlineKeyboardButton
|
||||
for _, placement := range page.Items {
|
||||
placementName := placement.CreativeName
|
||||
if placement.PlacementChannelTitle != nil && *placement.PlacementChannelTitle != "" {
|
||||
placementName = fmt.Sprintf("%s → %s", placement.CreativeName, *placement.PlacementChannelTitle)
|
||||
}
|
||||
row = append(row, bot.Button(placementName, fmt.Sprintf("placement:%s", placement.ID)))
|
||||
|
||||
// Второй placement в ряду (если есть)
|
||||
if i+1 < len(page.Items) {
|
||||
placement = page.Items[i+1]
|
||||
placementName = placement.CreativeName
|
||||
if placement.PlacementChannelTitle != nil && *placement.PlacementChannelTitle != "" {
|
||||
placementName = fmt.Sprintf("%s → %s", placement.CreativeName, *placement.PlacementChannelTitle)
|
||||
slots = append(slots, bot.Button(placementName, fmt.Sprintf("placement:%s", placement.ID)))
|
||||
}
|
||||
row = append(row, bot.Button(placementName, fmt.Sprintf("placement:%s", placement.ID)))
|
||||
if page.Pages >= 2 {
|
||||
for len(slots) < placementsPerPage {
|
||||
slots = append(slots, bot.Button(" ", "empty"))
|
||||
}
|
||||
}
|
||||
for i := 0; i < len(slots); i += 2 {
|
||||
row := []echotron.InlineKeyboardButton{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"))
|
||||
}
|
||||
|
||||
// Кнопка с номером страницы (неактивная)
|
||||
pageButton := bot.Button(fmt.Sprintf("• %d/%d •", s.CurrentPage+1, page.Pages), "page_info")
|
||||
navRow = append(navRow, pageButton)
|
||||
|
||||
// Правая стрелка (активная или неактивная)
|
||||
if s.CurrentPage+1 < page.Pages {
|
||||
navRow = append(navRow, bot.Button("→", "next"))
|
||||
} else {
|
||||
navRow = append(navRow, bot.Button("·", "empty"))
|
||||
}
|
||||
|
||||
if navRow := ui.BuildNavigationRow(ui.PaginationConfig{
|
||||
CurrentPage: s.CurrentPage,
|
||||
TotalPages: page.Pages,
|
||||
MiddleButtons: []echotron.InlineKeyboardButton{bot.Button(fmt.Sprintf("• %d/%d •", s.CurrentPage+1, page.Pages), "page_info")},
|
||||
}); navRow != nil {
|
||||
buttons = append(buttons, navRow)
|
||||
}
|
||||
}
|
||||
|
||||
// Кнопки управления
|
||||
buttons = append(buttons, bot.Row(
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"example.com/m/bot"
|
||||
"example.com/m/ui"
|
||||
"github.com/NicoNex/echotron/v3"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
@@ -107,11 +108,6 @@ func (s *MyProjects) renderProjects(b *bot.Bot, mode bot.RenderMode, jwt string)
|
||||
|
||||
%s`, navTitle, headerTitle, emptyStateText)
|
||||
} else {
|
||||
pageInfo := ""
|
||||
if page.Pages > 1 {
|
||||
pageInfo = fmt.Sprintf(" <i>(стр. %d/%d)</i>", page.Page, page.Pages)
|
||||
}
|
||||
|
||||
text = fmt.Sprintf(`<i>%s</i>
|
||||
|
||||
<b>%s</b>%s
|
||||
@@ -119,7 +115,7 @@ func (s *MyProjects) renderProjects(b *bot.Bot, mode bot.RenderMode, jwt string)
|
||||
%s`,
|
||||
navTitle,
|
||||
headerTitle,
|
||||
pageInfo,
|
||||
ui.FormatPageInfo(s.CurrentPage, page.Pages),
|
||||
nonEmptyText)
|
||||
}
|
||||
|
||||
@@ -153,22 +149,13 @@ func (s *MyProjects) renderProjects(b *bot.Bot, mode bot.RenderMode, jwt string)
|
||||
}
|
||||
|
||||
// Ряд: Пагинация + Добавить (динамическая ширина)
|
||||
var navRow []echotron.InlineKeyboardButton
|
||||
|
||||
// Левая стрелка (только если есть предыдущая страница)
|
||||
if s.CurrentPage > 0 {
|
||||
navRow = append(navRow, bot.Button("←", "prev"))
|
||||
}
|
||||
|
||||
// Кнопка "Добавить проект" всегда в центре
|
||||
navRow = append(navRow, bot.Button("+ Добавить", "add_project"))
|
||||
|
||||
// Правая стрелка (только если есть следующая страница)
|
||||
if s.CurrentPage+1 < page.Pages {
|
||||
navRow = append(navRow, bot.Button("→", "next"))
|
||||
}
|
||||
|
||||
if navRow := ui.BuildNavigationRow(ui.PaginationConfig{
|
||||
CurrentPage: s.CurrentPage,
|
||||
TotalPages: page.Pages,
|
||||
MiddleButtons: []echotron.InlineKeyboardButton{bot.Button("+ Добавить", "add_project")},
|
||||
}); navRow != nil {
|
||||
buttons = append(buttons, navRow)
|
||||
}
|
||||
|
||||
// Нижний ряд: Назад + Архив
|
||||
buttons = append(buttons, bot.Row(
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"time"
|
||||
|
||||
"example.com/m/bot"
|
||||
"example.com/m/ui"
|
||||
"github.com/NicoNex/echotron/v3"
|
||||
)
|
||||
|
||||
@@ -555,15 +556,10 @@ func (s *PurchaseOptionalDetails) renderParamChannels(b *bot.Bot, mode bot.Rende
|
||||
return
|
||||
}
|
||||
|
||||
start := s.ParamPage * perPage
|
||||
if start >= total {
|
||||
if s.ParamPage*perPage >= total {
|
||||
s.ParamPage = 0
|
||||
start = 0
|
||||
}
|
||||
end := start + perPage
|
||||
if end > total {
|
||||
end = total
|
||||
}
|
||||
start, end := ui.GetPageBounds(s.ParamPage, perPage, total)
|
||||
|
||||
var rows [][]echotron.InlineKeyboardButton
|
||||
for i := start; i < end; i++ {
|
||||
@@ -577,20 +573,11 @@ func (s *PurchaseOptionalDetails) renderParamChannels(b *bot.Bot, mode bot.Rende
|
||||
))
|
||||
}
|
||||
|
||||
pages := (total + perPage - 1) / perPage
|
||||
if pages > 1 {
|
||||
var navRow []echotron.InlineKeyboardButton
|
||||
if s.ParamPage > 0 {
|
||||
navRow = append(navRow, bot.Button("←", "param_prev"))
|
||||
} else {
|
||||
navRow = append(navRow, bot.Button("·", "empty"))
|
||||
}
|
||||
navRow = append(navRow, bot.Button(fmt.Sprintf("• %d/%d •", s.ParamPage+1, pages), "page_info"))
|
||||
if s.ParamPage+1 < pages {
|
||||
navRow = append(navRow, bot.Button("→", "param_next"))
|
||||
} else {
|
||||
navRow = append(navRow, bot.Button("·", "empty"))
|
||||
}
|
||||
pages := ui.CalculatePages(total, perPage)
|
||||
if navRow := ui.BuildNavigationRow(ui.PaginationConfig{
|
||||
CurrentPage: s.ParamPage,
|
||||
TotalPages: pages,
|
||||
}); navRow != nil {
|
||||
rows = append(rows, navRow)
|
||||
}
|
||||
|
||||
@@ -688,13 +675,13 @@ func (s *PurchaseOptionalDetails) handleParamCallback(b *bot.Bot, data string) b
|
||||
s.applyParamToAll(param)
|
||||
s.Enter(b, bot.EditMessage)
|
||||
return true
|
||||
case data == "param_prev":
|
||||
case data == "prev" && strings.HasSuffix(s.InputMode, "_channels"):
|
||||
if s.ParamPage > 0 {
|
||||
s.ParamPage--
|
||||
}
|
||||
s.Enter(b, bot.EditMessage)
|
||||
return true
|
||||
case data == "param_next":
|
||||
case data == "next" && strings.HasSuffix(s.InputMode, "_channels"):
|
||||
s.ParamPage++
|
||||
s.Enter(b, bot.EditMessage)
|
||||
return true
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
"example.com/m/adapter/backend"
|
||||
"example.com/m/bot"
|
||||
"example.com/m/ui"
|
||||
"github.com/NicoNex/echotron/v3"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
@@ -63,15 +64,10 @@ func (s *SelectChannelsForPurchase) renderChannelSelection(b *bot.Bot, mode bot.
|
||||
text += "\n"
|
||||
|
||||
const channelsPerPage = 6
|
||||
start := s.CurrentPage * channelsPerPage
|
||||
if start >= len(s.Channels) {
|
||||
if s.CurrentPage*channelsPerPage >= len(s.Channels) {
|
||||
s.CurrentPage = 0
|
||||
start = 0
|
||||
}
|
||||
end := start + channelsPerPage
|
||||
if end > len(s.Channels) {
|
||||
end = len(s.Channels)
|
||||
}
|
||||
start, end := ui.GetPageBounds(s.CurrentPage, channelsPerPage, len(s.Channels))
|
||||
|
||||
var slots []echotron.InlineKeyboardButton
|
||||
for i := start; i < end; i++ {
|
||||
@@ -99,20 +95,12 @@ func (s *SelectChannelsForPurchase) renderChannelSelection(b *bot.Bot, mode bot.
|
||||
buttons = append(buttons, row)
|
||||
}
|
||||
|
||||
pages := (len(s.Channels) + channelsPerPage - 1) / channelsPerPage
|
||||
if pages > 1 {
|
||||
var navRow []echotron.InlineKeyboardButton
|
||||
if s.CurrentPage > 0 {
|
||||
navRow = append(navRow, bot.Button("←", "prev_page"))
|
||||
} else {
|
||||
navRow = append(navRow, bot.Button("·", "empty"))
|
||||
}
|
||||
navRow = append(navRow, bot.Button(fmt.Sprintf("• %d/%d •", s.CurrentPage+1, pages), "page_info"))
|
||||
if s.CurrentPage+1 < pages {
|
||||
navRow = append(navRow, bot.Button("→", "next_page"))
|
||||
} else {
|
||||
navRow = append(navRow, bot.Button("·", "empty"))
|
||||
}
|
||||
pages := ui.CalculatePages(len(s.Channels), channelsPerPage)
|
||||
navRow := ui.BuildNavigationRow(ui.PaginationConfig{
|
||||
CurrentPage: s.CurrentPage,
|
||||
TotalPages: pages,
|
||||
})
|
||||
if navRow != nil {
|
||||
buttons = append(buttons, navRow)
|
||||
}
|
||||
|
||||
@@ -130,7 +118,7 @@ func (s *SelectChannelsForPurchase) renderChannelSelection(b *bot.Bot, mode bot.
|
||||
if len(s.Channels) > 0 {
|
||||
buttons = append(buttons, bot.Row(
|
||||
bot.Button("← Назад", "back"),
|
||||
bot.Button("→ Далее", "next"),
|
||||
bot.Button("→ Далее", "next_step"),
|
||||
))
|
||||
} else {
|
||||
buttons = append(buttons, bot.Row(
|
||||
@@ -176,7 +164,7 @@ func (s *SelectChannelsForPurchase) HandleCallback(b *bot.Bot, u *echotron.Updat
|
||||
BackState: s.BackState,
|
||||
}, bot.EditMessage)
|
||||
|
||||
case "next":
|
||||
case "next_step":
|
||||
if len(s.Channels) == 0 {
|
||||
b.Edit("❌ Добавьте хотя бы один канал", bot.Keyboard(
|
||||
bot.Row(bot.Button("← Назад", "back_to_select")),
|
||||
@@ -197,13 +185,13 @@ func (s *SelectChannelsForPurchase) HandleCallback(b *bot.Bot, u *echotron.Updat
|
||||
case "back_to_select":
|
||||
s.Enter(b, bot.EditMessage)
|
||||
|
||||
case "prev_page":
|
||||
case "prev":
|
||||
if s.CurrentPage > 0 {
|
||||
s.CurrentPage--
|
||||
}
|
||||
s.Enter(b, bot.EditMessage)
|
||||
|
||||
case "next_page":
|
||||
case "next":
|
||||
s.CurrentPage++
|
||||
s.Enter(b, bot.EditMessage)
|
||||
|
||||
@@ -215,7 +203,7 @@ func (s *SelectChannelsForPurchase) HandleCallback(b *bot.Bot, u *echotron.Updat
|
||||
s.Channels = append(s.Channels[:index], s.Channels[index+1:]...)
|
||||
if s.CurrentPage > 0 {
|
||||
channelsPerPage := 6
|
||||
pages := (len(s.Channels) + channelsPerPage - 1) / channelsPerPage
|
||||
pages := ui.CalculatePages(len(s.Channels), channelsPerPage)
|
||||
if s.CurrentPage >= pages {
|
||||
s.CurrentPage = pages - 1
|
||||
}
|
||||
@@ -262,7 +250,8 @@ func (s *SelectChannelsForPurchase) createPurchase(b *bot.Bot, jwt string) {
|
||||
}
|
||||
|
||||
// Показываем успешное создание
|
||||
successText := fmt.Sprintf(`<i>… › Создание › Готово</i>
|
||||
successText := fmt.Sprintf(`
|
||||
<i>… › Создание › Готово</i>
|
||||
|
||||
<b>Закуп создан</b>
|
||||
|
||||
@@ -270,7 +259,8 @@ func (s *SelectChannelsForPurchase) createPurchase(b *bot.Bot, jwt string) {
|
||||
▪ <b>Креатив:</b> %s
|
||||
▪ <b>Каналов:</b> %d
|
||||
|
||||
Закуп создан и готов к работе`, purchase.ID, purchase.CreativeID, len(purchase.Channels))
|
||||
Закуп создан и готов к работе
|
||||
`, purchase.ID, purchase.CreativeID, len(purchase.Channels))
|
||||
|
||||
b.Edit(successText, bot.Keyboard(
|
||||
bot.Row(bot.Button("● Готово", "done")),
|
||||
|
||||
93
telegram_bot/ui/pagination.go
Normal file
93
telegram_bot/ui/pagination.go
Normal file
@@ -0,0 +1,93 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"example.com/m/bot"
|
||||
"github.com/NicoNex/echotron/v3"
|
||||
)
|
||||
|
||||
// PaginationConfig конфигурация для построения навигационного ряда пагинации
|
||||
type PaginationConfig struct {
|
||||
CurrentPage int
|
||||
TotalPages int // Для API пагинации (из page.Pages)
|
||||
TotalItems int // Для локальной пагинации (len(array))
|
||||
ItemsPerPage int // Для расчета TotalPages из TotalItems
|
||||
PrevCallback string // По умолчанию "prev"
|
||||
NextCallback string // По умолчанию "next"
|
||||
MiddleButtons []echotron.InlineKeyboardButton // Опциональные кнопки в центре (например, "+ Добавить")
|
||||
}
|
||||
|
||||
// BuildNavigationRow создает навигационный ряд с динамической шириной.
|
||||
// Показывает ряд только если страниц > 1.
|
||||
// Возвращает nil если пагинация не нужна.
|
||||
func BuildNavigationRow(config PaginationConfig) []echotron.InlineKeyboardButton {
|
||||
// Вычисляем TotalPages из TotalItems если не задан
|
||||
totalPages := config.TotalPages
|
||||
if totalPages == 0 && config.TotalItems > 0 && config.ItemsPerPage > 0 {
|
||||
totalPages = CalculatePages(config.TotalItems, config.ItemsPerPage)
|
||||
}
|
||||
|
||||
// Устанавливаем значения по умолчанию для callbacks
|
||||
prevCallback := config.PrevCallback
|
||||
if prevCallback == "" {
|
||||
prevCallback = "prev"
|
||||
}
|
||||
nextCallback := config.NextCallback
|
||||
if nextCallback == "" {
|
||||
nextCallback = "next"
|
||||
}
|
||||
|
||||
var row []echotron.InlineKeyboardButton
|
||||
|
||||
// Левая стрелка (только если есть предыдущая страница)
|
||||
if totalPages > 1 && config.CurrentPage > 0 {
|
||||
row = append(row, bot.Button("←", prevCallback))
|
||||
}
|
||||
|
||||
// Средние кнопки (если есть)
|
||||
if len(config.MiddleButtons) > 0 {
|
||||
row = append(row, config.MiddleButtons...)
|
||||
}
|
||||
|
||||
// Правая стрелка (только если есть следующая страница)
|
||||
if totalPages > 1 && config.CurrentPage+1 < totalPages {
|
||||
row = append(row, bot.Button("→", nextCallback))
|
||||
}
|
||||
|
||||
if len(row) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return row
|
||||
}
|
||||
|
||||
// CalculatePages вычисляет количество страниц из общего количества элементов
|
||||
func CalculatePages(totalItems, itemsPerPage int) int {
|
||||
if itemsPerPage <= 0 {
|
||||
return 0
|
||||
}
|
||||
return (totalItems + itemsPerPage - 1) / itemsPerPage
|
||||
}
|
||||
|
||||
// GetPageBounds возвращает start/end индексы для текущей страницы.
|
||||
// Автоматически сбрасывает на первую страницу если currentPage выходит за границы.
|
||||
func GetPageBounds(currentPage, itemsPerPage, totalItems int) (start, end int) {
|
||||
start = currentPage * itemsPerPage
|
||||
if start >= totalItems {
|
||||
start = 0
|
||||
}
|
||||
end = start + itemsPerPage
|
||||
if end > totalItems {
|
||||
end = totalItems
|
||||
}
|
||||
return start, end
|
||||
}
|
||||
|
||||
// FormatPageInfo форматирует текст индикатора страницы для заголовка
|
||||
func FormatPageInfo(currentPage, totalPages int) string {
|
||||
if totalPages <= 1 {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf(" <i>(стр. %d/%d)</i>", currentPage+1, totalPages)
|
||||
}
|
||||
Reference in New Issue
Block a user