улучшение ui
This commit is contained in:
@@ -5,7 +5,6 @@ import (
|
||||
|
||||
"example.com/m/bot"
|
||||
"github.com/NicoNex/echotron/v3"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type AcceptWorkspaceInvite struct {
|
||||
@@ -18,38 +17,29 @@ const msgAcceptWorkspaceInviteSuccessfully = `
|
||||
`
|
||||
|
||||
func (s *AcceptWorkspaceInvite) Enter(b *bot.Bot, mode bot.RenderMode) {
|
||||
// Получаем JWT токен
|
||||
jwt, err := b.Backend.GetJWTByTelegramID(context.Background(), b.ChatID)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to get JWT")
|
||||
b.SendNew("❌ Ошибка авторизации. Попробуйте /start", bot.Keyboard())
|
||||
jwt := b.Session.JWT
|
||||
if jwt == "" {
|
||||
b.SendNew("<b>❌ Ошибка авторизации</b>\n\nПопробуйте /start", emptyKeyboard)
|
||||
return
|
||||
}
|
||||
|
||||
// Принимаем приглашение
|
||||
err = b.Backend.AcceptWorkspaceInvite(context.Background(), jwt, s.InviteID)
|
||||
err := b.Backend.AcceptWorkspaceInvite(context.Background(), jwt, s.InviteID)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Str("invite_id", s.InviteID).Msg("Failed to accept workspace invite")
|
||||
b.SendNew("❌ Не удалось принять приглашение. Возможно, оно уже было использовано.", bot.Keyboard())
|
||||
b.SendNew("❌ Не удалось принять приглашение. Возможно, оно уже было использовано.", emptyKeyboard)
|
||||
return
|
||||
}
|
||||
|
||||
b.SendNew(msgAcceptWorkspaceInviteSuccessfully, bot.Keyboard(
|
||||
bot.Row(bot.Button("🏠 Главное меню", "menu")),
|
||||
))
|
||||
kb := Keyboard(
|
||||
Row(Button("Главное меню", "menu")),
|
||||
)
|
||||
|
||||
b.SendNew(msgAcceptWorkspaceInviteSuccessfully, kb)
|
||||
}
|
||||
|
||||
func (s *AcceptWorkspaceInvite) HandleCallback(b *bot.Bot, u *echotron.Update) {
|
||||
if u.CallbackQuery == nil || u.CallbackQuery.Data == "" {
|
||||
return
|
||||
}
|
||||
func (s *AcceptWorkspaceInvite) HandleCallback(b *bot.Bot, u *echotron.Update) {}
|
||||
|
||||
if u.CallbackQuery.Data == "menu" {
|
||||
b.SetState(&MainMenu{}, bot.EditMessage)
|
||||
}
|
||||
return
|
||||
func (s *AcceptWorkspaceInvite) HandleMessage(_ *bot.Bot, _ *echotron.Update) {}
|
||||
|
||||
func (s *AcceptWorkspaceInvite) Handle(b *bot.Bot, _ *echotron.Update) {
|
||||
b.SetState(&MainMenu{}, bot.EditMessage)
|
||||
}
|
||||
|
||||
func (s *AcceptWorkspaceInvite) HandleMessage(_ *bot.Bot, _ *echotron.Update) { return }
|
||||
|
||||
func (s *AcceptWorkspaceInvite) Handle(_ *bot.Bot, _ *echotron.Update) { return }
|
||||
|
||||
@@ -23,8 +23,8 @@ type AddCreative struct {
|
||||
WaitingForTextEdit bool // Ждем редактирования текста креатива
|
||||
}
|
||||
|
||||
func (s *AddCreative) Enter(b *bot.Bot, mode bot.RenderMode) {
|
||||
text := `<i>… › Креативы › Добавление</i>
|
||||
const msgWaitCreative = `
|
||||
<i>… › Креативы › Добавление</i>
|
||||
|
||||
<b>+ Добавить креатив</b>
|
||||
|
||||
@@ -32,15 +32,13 @@ func (s *AddCreative) Enter(b *bot.Bot, mode bot.RenderMode) {
|
||||
|
||||
• Обрабатываем любое сообщение Telegram
|
||||
• Отправляйте сразу оформленный пост
|
||||
• На следующем шаге можно добавить кнопки или изменить контент`
|
||||
• На следующем шаге можно добавить кнопки или изменить контент
|
||||
`
|
||||
|
||||
buttons := [][]echotron.InlineKeyboardButton{
|
||||
bot.Row(bot.Button("« Отмена", "cancel")),
|
||||
}
|
||||
|
||||
keyboard := bot.Keyboard(buttons...)
|
||||
func (s *AddCreative) Enter(b *bot.Bot, mode bot.RenderMode) {
|
||||
kb := Keyboard(Row(Button("« Отмена", "cancel")))
|
||||
s.WaitingForCreative = true
|
||||
b.Render(text, keyboard, mode)
|
||||
b.Render(msgWaitCreative, kb, mode)
|
||||
}
|
||||
|
||||
func (s *AddCreative) HandleCallback(b *bot.Bot, u *echotron.Update) {
|
||||
@@ -69,7 +67,7 @@ func (s *AddCreative) HandleCallback(b *bot.Bot, u *echotron.Update) {
|
||||
<b>Пример:</b>
|
||||
<i>Присоединяйтесь к нашему каналу!
|
||||
https://t.me/+AbCdEfGhIjKlMn</i>`, [][]echotron.InlineKeyboardButton{
|
||||
bot.Row(bot.Button("« Отмена", "cancel_edit")),
|
||||
Row(Button("« Отмена", "cancel_edit")),
|
||||
})
|
||||
|
||||
case data == "add_button":
|
||||
@@ -86,11 +84,11 @@ https://t.me/+AbCdEfGhIjKlMn</i>`, [][]echotron.InlineKeyboardButton{
|
||||
• Написать нам
|
||||
|
||||
<i>Ссылка на канал подставится автоматически при создании закупа.</i>`, [][]echotron.InlineKeyboardButton{
|
||||
bot.Row(
|
||||
bot.Button("✓ Ссылка на канал", "button_type_invite"),
|
||||
bot.Button("Своя ссылка", "button_type_custom"),
|
||||
Row(
|
||||
Button("✓ Ссылка на канал", "button_type_invite"),
|
||||
Button("Своя ссылка", "button_type_custom"),
|
||||
),
|
||||
bot.Row(bot.Button("« Отмена", "cancel_add_button")),
|
||||
Row(Button("« Отмена", "cancel_add_button")),
|
||||
})
|
||||
|
||||
case data == "add_media":
|
||||
@@ -102,7 +100,7 @@ https://t.me/+AbCdEfGhIjKlMn</i>`, [][]echotron.InlineKeyboardButton{
|
||||
Отправьте <b>фото, видео или GIF</b>
|
||||
|
||||
<i>Это медиа будет отображаться в креативе вместе с текстом</i>`, [][]echotron.InlineKeyboardButton{
|
||||
bot.Row(bot.Button("« Отмена", "cancel_media")),
|
||||
Row(Button("« Отмена", "cancel_media")),
|
||||
})
|
||||
|
||||
case data == "delete_media":
|
||||
@@ -150,11 +148,11 @@ https://t.me/+AbCdEfGhIjKlMn</i>`, [][]echotron.InlineKeyboardButton{
|
||||
• Написать нам
|
||||
|
||||
<i>Ссылка на канал подставится автоматически при создании закупа.</i>`, [][]echotron.InlineKeyboardButton{
|
||||
bot.Row(
|
||||
bot.Button("✓ Ссылка на канал", "button_type_invite"),
|
||||
bot.Button("Своя ссылка", "button_type_custom"),
|
||||
Row(
|
||||
Button("✓ Ссылка на канал", "button_type_invite"),
|
||||
Button("Своя ссылка", "button_type_custom"),
|
||||
),
|
||||
bot.Row(bot.Button("« Отмена", "cancel_add_button")),
|
||||
Row(Button("« Отмена", "cancel_add_button")),
|
||||
})
|
||||
|
||||
case data == "button_type_custom":
|
||||
@@ -168,11 +166,11 @@ https://t.me/+AbCdEfGhIjKlMn</i>`, [][]echotron.InlineKeyboardButton{
|
||||
• Написать нам
|
||||
|
||||
<i>Ссылка на канал подставится автоматически при создании закупа.</i>`, [][]echotron.InlineKeyboardButton{
|
||||
bot.Row(
|
||||
bot.Button("Ссылка на канал", "button_type_invite"),
|
||||
bot.Button("✓ Своя ссылка", "button_type_custom"),
|
||||
Row(
|
||||
Button("Ссылка на канал", "button_type_invite"),
|
||||
Button("✓ Своя ссылка", "button_type_custom"),
|
||||
),
|
||||
bot.Row(bot.Button("« Отмена", "cancel_add_button")),
|
||||
Row(Button("« Отмена", "cancel_add_button")),
|
||||
})
|
||||
|
||||
case data == "cancel_edit":
|
||||
@@ -321,7 +319,7 @@ URL должен начинаться с <code>https://</code>
|
||||
<i>Например:</i>
|
||||
<code>https://example.com</code>
|
||||
<code>https://t.me/yourchannel</code>`, [][]echotron.InlineKeyboardButton{
|
||||
bot.Row(bot.Button("« Отмена", "cancel_add_button")),
|
||||
Row(Button("« Отмена", "cancel_add_button")),
|
||||
})
|
||||
} else {
|
||||
s.Buttons = append(s.Buttons, InlineButton{
|
||||
@@ -359,7 +357,7 @@ URL должен начинаться с <code>https://</code>
|
||||
URL должен начинаться с <code>https://</code>
|
||||
|
||||
Попробуйте ещё раз.`, [][]echotron.InlineKeyboardButton{
|
||||
bot.Row(bot.Button("« Отмена", "cancel_add_button")),
|
||||
Row(Button("« Отмена", "cancel_add_button")),
|
||||
})
|
||||
return
|
||||
}
|
||||
@@ -472,23 +470,20 @@ func (s *AddCreative) extractCreativeFromMessage(message *echotron.Message) {
|
||||
|
||||
// generateCreativeName генерирует название из текста или дефолтное
|
||||
func (s *AddCreative) generateCreativeName() string {
|
||||
// Если есть текст, берем первые слова
|
||||
if s.Text != nil && *s.Text != "" {
|
||||
// Удаляем ВСЕ HTML теги с помощью regex
|
||||
re := regexp.MustCompile(`<[^>]*>`)
|
||||
cleanText := re.ReplaceAllString(*s.Text, "")
|
||||
|
||||
// Берем первые 50 символов
|
||||
runes := []rune(strings.TrimSpace(cleanText))
|
||||
if len(runes) > 50 {
|
||||
return string(runes[:50]) + "..."
|
||||
if len(runes) > 10 {
|
||||
return string(runes[:10]) + "..."
|
||||
}
|
||||
if len(runes) > 0 {
|
||||
return string(runes)
|
||||
}
|
||||
}
|
||||
|
||||
// Дефолтное название
|
||||
return "Новый креатив"
|
||||
}
|
||||
|
||||
@@ -505,16 +500,16 @@ func (s *AddCreative) buildEditorButtons(confirmText, cancelCallback string) [][
|
||||
var buttons [][]echotron.InlineKeyboardButton
|
||||
|
||||
// Кнопки редактирования
|
||||
buttons = append(buttons, bot.Row(
|
||||
bot.Button("✎ Текст", "edit_text"),
|
||||
bot.Button("+ Медиа", "add_media"),
|
||||
buttons = append(buttons, Row(
|
||||
Button("✎ Текст", "edit_text"),
|
||||
Button("+ Медиа", "add_media"),
|
||||
))
|
||||
buttons = append(buttons, bot.Row(bot.Button("+ Добавить кнопку", "add_button")))
|
||||
buttons = append(buttons, Row(Button("+ Добавить кнопку", "add_button")))
|
||||
|
||||
// Кнопка удаления медиа (если медиа добавлено)
|
||||
if s.MediaFileID != "" {
|
||||
buttons = append(buttons, bot.Row(
|
||||
bot.Button("⌫ Убрать медиа", "delete_media"),
|
||||
buttons = append(buttons, Row(
|
||||
Button("⌫ Убрать медиа", "delete_media"),
|
||||
))
|
||||
}
|
||||
|
||||
@@ -522,7 +517,7 @@ func (s *AddCreative) buildEditorButtons(confirmText, cancelCallback string) [][
|
||||
if len(s.Buttons) > 0 {
|
||||
var row []echotron.InlineKeyboardButton
|
||||
for i, btn := range s.Buttons {
|
||||
row = append(row, bot.Button(
|
||||
row = append(row, Button(
|
||||
fmt.Sprintf(`⌫ Убрать «%s»`, btn.Text),
|
||||
fmt.Sprintf("delete_button:%d", i),
|
||||
))
|
||||
@@ -540,13 +535,13 @@ func (s *AddCreative) buildEditorButtons(confirmText, cancelCallback string) [][
|
||||
|
||||
// Финальные кнопки
|
||||
if s.Text != nil {
|
||||
buttons = append(buttons, bot.Row(
|
||||
bot.Button("← "+cancelCallback, "cancel"),
|
||||
bot.Button(confirmText, "confirm_create"),
|
||||
buttons = append(buttons, Row(
|
||||
Button("← "+cancelCallback, "cancel"),
|
||||
Button(confirmText, "confirm_create"),
|
||||
))
|
||||
} else {
|
||||
buttons = append(buttons, bot.Row(
|
||||
bot.Button("← "+cancelCallback, "cancel"),
|
||||
buttons = append(buttons, Row(
|
||||
Button("← "+cancelCallback, "cancel"),
|
||||
))
|
||||
}
|
||||
|
||||
@@ -592,7 +587,7 @@ func (s *AddCreative) createCreative(b *bot.Bot) {
|
||||
jwt := b.Session.JWT
|
||||
if jwt == "" {
|
||||
log.Error().Msg("JWT is empty in session")
|
||||
b.SendNew("<b>❌ Ошибка авторизации</b>\n\nПопробуйте /start login", bot.Keyboard())
|
||||
b.SendNew("<b>❌ Ошибка авторизации</b>\n\nПопробуйте /start login", Keyboard())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -692,7 +687,7 @@ https://t.me/+AbCdEfGhIjKlMn</i>
|
||||
🔘 <b>Кнопок добавлено:</b> %d`, len(s.Buttons))
|
||||
}
|
||||
|
||||
b.SendNew(successText, bot.Keyboard())
|
||||
b.SendNew(successText, Keyboard())
|
||||
|
||||
// Возвращаемся на экран креативов
|
||||
if s.BackState != nil {
|
||||
|
||||
@@ -30,10 +30,10 @@ type AddProject struct {
|
||||
|
||||
func (s *AddProject) Enter(b *bot.Bot, mode bot.RenderMode) {
|
||||
buttons := [][]echotron.InlineKeyboardButton{
|
||||
bot.Row(bot.Button("← Назад", "back")),
|
||||
Row(Button("← Назад", "back")),
|
||||
}
|
||||
|
||||
keyboard := bot.Keyboard(buttons...)
|
||||
keyboard := Keyboard(buttons...)
|
||||
b.Render(addProjectInstructionText, keyboard, mode)
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ func (s *AddPurchase) Enter(b *bot.Bot, mode bot.RenderMode) {
|
||||
jwt := b.Session.JWT
|
||||
if jwt == "" {
|
||||
log.Error().Msg("JWT is empty in session")
|
||||
b.SendNew("❌ Ошибка авторизации. Попробуйте /start", bot.Keyboard())
|
||||
b.SendNew("❌ Ошибка авторизации. Попробуйте /start", Keyboard())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -55,11 +55,11 @@ func (s *AddPurchase) renderSelection(b *bot.Bot, mode bot.RenderMode, jwt strin
|
||||
}
|
||||
|
||||
if s.ActivePicker == "" {
|
||||
buttons = append(buttons, bot.Row(
|
||||
bot.Button(projectLabel, "pick_project"),
|
||||
buttons = append(buttons, Row(
|
||||
Button(projectLabel, "pick_project"),
|
||||
))
|
||||
buttons = append(buttons, bot.Row(
|
||||
bot.Button(creativeLabel, "pick_creative"),
|
||||
buttons = append(buttons, Row(
|
||||
Button(creativeLabel, "pick_creative"),
|
||||
))
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ func (s *AddPurchase) renderSelection(b *bot.Bot, mode bot.RenderMode, jwt strin
|
||||
)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to get projects")
|
||||
b.SendNew("❌ Не удалось загрузить проекты", bot.Keyboard())
|
||||
b.SendNew("❌ Не удалось загрузить проекты", Keyboard())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -90,11 +90,11 @@ func (s *AddPurchase) renderSelection(b *bot.Bot, mode bot.RenderMode, jwt strin
|
||||
|
||||
var slots []echotron.InlineKeyboardButton
|
||||
for _, project := range page.Items {
|
||||
slots = append(slots, bot.Button(project.Title, fmt.Sprintf("project:%s", project.ID)))
|
||||
slots = append(slots, Button(project.Title, fmt.Sprintf("project:%s", project.ID)))
|
||||
}
|
||||
if page.Pages >= 2 {
|
||||
for len(slots) < addPurchaseProjectsPerPage {
|
||||
slots = append(slots, bot.Button(" ", "empty"))
|
||||
slots = append(slots, Button(" ", "empty"))
|
||||
}
|
||||
}
|
||||
for i := 0; i < len(slots); i += 2 {
|
||||
@@ -102,7 +102,7 @@ func (s *AddPurchase) renderSelection(b *bot.Bot, mode bot.RenderMode, jwt strin
|
||||
if i+1 < len(slots) {
|
||||
row = append(row, slots[i+1])
|
||||
} else {
|
||||
row = append(row, bot.Button(" ", "empty"))
|
||||
row = append(row, Button(" ", "empty"))
|
||||
}
|
||||
buttons = append(buttons, row)
|
||||
}
|
||||
@@ -115,15 +115,15 @@ func (s *AddPurchase) renderSelection(b *bot.Bot, mode bot.RenderMode, jwt strin
|
||||
}
|
||||
}
|
||||
|
||||
buttons = append(buttons, bot.Row(
|
||||
bot.Button("← Назад", "back_to_selection"),
|
||||
buttons = append(buttons, Row(
|
||||
Button("← Назад", "back_to_selection"),
|
||||
))
|
||||
|
||||
case "creative_list":
|
||||
if s.ProjectID == "" {
|
||||
text += "Сначала выберите проект, чтобы увидеть список креативов.\n\n"
|
||||
buttons = append(buttons, bot.Row(
|
||||
bot.Button("← Назад", "back_to_selection"),
|
||||
buttons = append(buttons, Row(
|
||||
Button("← Назад", "back_to_selection"),
|
||||
))
|
||||
break
|
||||
}
|
||||
@@ -139,7 +139,7 @@ func (s *AddPurchase) renderSelection(b *bot.Bot, mode bot.RenderMode, jwt strin
|
||||
)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to get creatives")
|
||||
b.SendNew("❌ Не удалось загрузить креативы", bot.Keyboard())
|
||||
b.SendNew("❌ Не удалось загрузить креативы", Keyboard())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -157,11 +157,11 @@ func (s *AddPurchase) renderSelection(b *bot.Bot, mode bot.RenderMode, jwt strin
|
||||
|
||||
var slots []echotron.InlineKeyboardButton
|
||||
for _, creative := range page.Items {
|
||||
slots = append(slots, bot.Button(creative.Name, fmt.Sprintf("creative:%s", creative.ID)))
|
||||
slots = append(slots, Button(creative.Name, fmt.Sprintf("creative:%s", creative.ID)))
|
||||
}
|
||||
if page.Pages >= 2 {
|
||||
for len(slots) < addPurchaseCreativesPerPage {
|
||||
slots = append(slots, bot.Button(" ", "empty"))
|
||||
slots = append(slots, Button(" ", "empty"))
|
||||
}
|
||||
}
|
||||
for i := 0; i < len(slots); i += 2 {
|
||||
@@ -169,7 +169,7 @@ func (s *AddPurchase) renderSelection(b *bot.Bot, mode bot.RenderMode, jwt strin
|
||||
if i+1 < len(slots) {
|
||||
row = append(row, slots[i+1])
|
||||
} else {
|
||||
row = append(row, bot.Button(" ", "empty"))
|
||||
row = append(row, Button(" ", "empty"))
|
||||
}
|
||||
buttons = append(buttons, row)
|
||||
}
|
||||
@@ -182,25 +182,25 @@ func (s *AddPurchase) renderSelection(b *bot.Bot, mode bot.RenderMode, jwt strin
|
||||
}
|
||||
}
|
||||
|
||||
buttons = append(buttons, bot.Row(
|
||||
bot.Button("← Назад", "back_to_selection"),
|
||||
buttons = append(buttons, Row(
|
||||
Button("← Назад", "back_to_selection"),
|
||||
))
|
||||
}
|
||||
|
||||
if s.ActivePicker == "" {
|
||||
if s.ProjectID != "" && s.CreativeID != "" {
|
||||
buttons = append(buttons, bot.Row(
|
||||
bot.Button("← Отмена", "back"),
|
||||
bot.Button("→ Далее", "next"),
|
||||
buttons = append(buttons, Row(
|
||||
Button("← Отмена", "back"),
|
||||
Button("→ Далее", "next"),
|
||||
))
|
||||
} else {
|
||||
buttons = append(buttons, bot.Row(
|
||||
bot.Button("← Отмена", "back"),
|
||||
buttons = append(buttons, Row(
|
||||
Button("← Отмена", "back"),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
keyboard := bot.Keyboard(buttons...)
|
||||
keyboard := Keyboard(buttons...)
|
||||
b.Render(text, keyboard, mode)
|
||||
}
|
||||
|
||||
|
||||
@@ -24,11 +24,10 @@ type CreativeDetails struct {
|
||||
}
|
||||
|
||||
func (s *CreativeDetails) 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("<b>❌ Ошибка авторизации</b>\n\nПопробуйте /start", bot.Keyboard())
|
||||
b.SendNew("<b>❌ Ошибка авторизации</b>\n\nПопробуйте /start", Keyboard())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -36,8 +35,8 @@ func (s *CreativeDetails) Enter(b *bot.Bot, mode bot.RenderMode) {
|
||||
creative, err := b.Backend.GetCreative(context.Background(), jwt, s.WorkspaceID, s.CreativeID)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Str("creative_id", s.CreativeID).Msg("Failed to get creative")
|
||||
b.SendNew("<b>❌ Не удалось загрузить креатив</b>", bot.Keyboard(
|
||||
bot.Row(bot.Button("← Назад", "back")),
|
||||
b.SendNew("<b>❌ Не удалось загрузить креатив</b>", Keyboard(
|
||||
Row(Button("← Назад", "back")),
|
||||
))
|
||||
return
|
||||
}
|
||||
@@ -96,7 +95,7 @@ func (s *CreativeDetails) HandleCallback(b *bot.Bot, u *echotron.Update) {
|
||||
<b>Пример:</b>
|
||||
<i>Присоединяйтесь к нашему каналу!
|
||||
https://t.me/+AbCdEfGhIjKlMn</i>`, [][]echotron.InlineKeyboardButton{
|
||||
bot.Row(bot.Button("« Отмена редактирования", "cancel_edit")),
|
||||
Row(Button("« Отмена редактирования", "cancel_edit")),
|
||||
})
|
||||
|
||||
case data == "add_button":
|
||||
@@ -111,11 +110,11 @@ https://t.me/+AbCdEfGhIjKlMn</i>`, [][]echotron.InlineKeyboardButton{
|
||||
• Написать нам
|
||||
|
||||
<i>Ссылка на канал подставится автоматически при создании закупа.</i>`, [][]echotron.InlineKeyboardButton{
|
||||
bot.Row(
|
||||
bot.Button("✓ Ссылка на канал", "button_type_invite"),
|
||||
bot.Button("Своя ссылка", "button_type_custom"),
|
||||
Row(
|
||||
Button("✓ Ссылка на канал", "button_type_invite"),
|
||||
Button("Своя ссылка", "button_type_custom"),
|
||||
),
|
||||
bot.Row(bot.Button("« Отмена", "cancel_add_button")),
|
||||
Row(Button("« Отмена", "cancel_add_button")),
|
||||
})
|
||||
|
||||
case data == "add_media":
|
||||
@@ -125,7 +124,7 @@ https://t.me/+AbCdEfGhIjKlMn</i>`, [][]echotron.InlineKeyboardButton{
|
||||
Отправьте <b>фото, видео или GIF</b>
|
||||
|
||||
<i>Это медиа будет отображаться в креативе вместе с текстом</i>`, [][]echotron.InlineKeyboardButton{
|
||||
bot.Row(bot.Button("« Отмена", "cancel_media")),
|
||||
Row(Button("« Отмена", "cancel_media")),
|
||||
})
|
||||
|
||||
case data == "delete_media":
|
||||
@@ -161,11 +160,11 @@ https://t.me/+AbCdEfGhIjKlMn</i>`, [][]echotron.InlineKeyboardButton{
|
||||
• Написать нам
|
||||
|
||||
<i>Ссылка на канал подставится автоматически при создании закупа.</i>`, [][]echotron.InlineKeyboardButton{
|
||||
bot.Row(
|
||||
bot.Button("✓ Ссылка на канал", "button_type_invite"),
|
||||
bot.Button("Своя ссылка", "button_type_custom"),
|
||||
Row(
|
||||
Button("✓ Ссылка на канал", "button_type_invite"),
|
||||
Button("Своя ссылка", "button_type_custom"),
|
||||
),
|
||||
bot.Row(bot.Button("« Отмена", "cancel_add_button")),
|
||||
Row(Button("« Отмена", "cancel_add_button")),
|
||||
})
|
||||
|
||||
case data == "button_type_custom":
|
||||
@@ -179,11 +178,11 @@ https://t.me/+AbCdEfGhIjKlMn</i>`, [][]echotron.InlineKeyboardButton{
|
||||
• Написать нам
|
||||
|
||||
<i>Ссылка на канал подставится автоматически при создании закупа.</i>`, [][]echotron.InlineKeyboardButton{
|
||||
bot.Row(
|
||||
bot.Button("Ссылка на канал", "button_type_invite"),
|
||||
bot.Button("✓ Своя ссылка", "button_type_custom"),
|
||||
Row(
|
||||
Button("Ссылка на канал", "button_type_invite"),
|
||||
Button("✓ Своя ссылка", "button_type_custom"),
|
||||
),
|
||||
bot.Row(bot.Button("« Отмена", "cancel_add_button")),
|
||||
Row(Button("« Отмена", "cancel_add_button")),
|
||||
})
|
||||
|
||||
case data == "cancel_edit", data == "cancel_media":
|
||||
@@ -298,7 +297,7 @@ func (s *CreativeDetails) HandleMessage(b *bot.Bot, u *echotron.Update) {
|
||||
|
||||
<i>Например:</i>
|
||||
<code>https://example.com</code>`, [][]echotron.InlineKeyboardButton{
|
||||
bot.Row(bot.Button("« Отмена", "cancel_add_button")),
|
||||
Row(Button("« Отмена", "cancel_add_button")),
|
||||
})
|
||||
} else {
|
||||
s.Buttons = append(s.Buttons, InlineButton{
|
||||
@@ -329,7 +328,7 @@ func (s *CreativeDetails) HandleMessage(b *bot.Bot, u *echotron.Update) {
|
||||
URL должен начинаться с <code>http://</code> или <code>https://</code>
|
||||
|
||||
Попробуйте ещё раз:`, [][]echotron.InlineKeyboardButton{
|
||||
bot.Row(bot.Button("« Отмена", "cancel_add_button")),
|
||||
Row(Button("« Отмена", "cancel_add_button")),
|
||||
})
|
||||
return
|
||||
}
|
||||
@@ -394,7 +393,7 @@ func (s *CreativeDetails) showManagementPanelWithDelete(b *bot.Bot) {
|
||||
}
|
||||
|
||||
// Добавляем кнопку удаления креатива
|
||||
deleteButton := bot.Row(bot.Button("⌦ Удалить креатив", "delete_creative"))
|
||||
deleteButton := Row(Button("⌦ Удалить креатив", "delete_creative"))
|
||||
|
||||
s.ShowManagementPanel(b, confirmText, confirmCallback, "cancel", deleteButton)
|
||||
}
|
||||
@@ -404,7 +403,7 @@ func (s *CreativeDetails) updateCreative(b *bot.Bot) {
|
||||
jwt := b.Session.JWT
|
||||
if jwt == "" {
|
||||
log.Error().Msg("JWT is empty in session")
|
||||
b.SendNew("<b>❌ Ошибка авторизации</b>\n\nПопробуйте /start", bot.Keyboard())
|
||||
b.SendNew("<b>❌ Ошибка авторизации</b>\n\nПопробуйте /start", Keyboard())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -427,8 +426,8 @@ func (s *CreativeDetails) updateCreative(b *bot.Bot) {
|
||||
log.Error().Err(err).Msg("Failed to download creative media")
|
||||
// Показываем ошибку через панель управления
|
||||
s.ShowControlPanel(b, "<b>❌ Не удалось загрузить медиа</b>\n\nПопробуйте удалить медиа и добавить заново, или нажмите \"Сохранить\" без изменения медиа.", [][]echotron.InlineKeyboardButton{
|
||||
bot.Row(bot.Button("← Назад", "back")),
|
||||
bot.Row(bot.Button("✓ Сохранить без медиа", "confirm_save")),
|
||||
Row(Button("← Назад", "back")),
|
||||
Row(Button("✓ Сохранить без медиа", "confirm_save")),
|
||||
})
|
||||
return
|
||||
}
|
||||
@@ -461,8 +460,8 @@ func (s *CreativeDetails) updateCreative(b *bot.Bot) {
|
||||
log.Error().Err(err).Str("creative_id", s.CreativeID).Msg("Failed to update creative")
|
||||
// Показываем ошибку через панель управления
|
||||
s.ShowControlPanel(b, "<b>❌ Не удалось сохранить изменения</b>\n\nПроверьте подключение и попробуйте снова.", [][]echotron.InlineKeyboardButton{
|
||||
bot.Row(bot.Button("← Назад", "back")),
|
||||
bot.Row(bot.Button("✓ Попробовать снова", "confirm_save")),
|
||||
Row(Button("← Назад", "back")),
|
||||
Row(Button("✓ Попробовать снова", "confirm_save")),
|
||||
})
|
||||
return
|
||||
}
|
||||
@@ -471,7 +470,7 @@ func (s *CreativeDetails) updateCreative(b *bot.Bot) {
|
||||
|
||||
// Возвращаемся назад
|
||||
if s.BackState != nil {
|
||||
b.SendNew("✅ Креатив успешно обновлен!", bot.Keyboard())
|
||||
b.SendNew("✅ Креатив успешно обновлен!", Keyboard())
|
||||
b.SetState(s.BackState, bot.EditMessage)
|
||||
}
|
||||
}
|
||||
@@ -481,7 +480,7 @@ func (s *CreativeDetails) deleteCreative(b *bot.Bot) {
|
||||
jwt := b.Session.JWT
|
||||
if jwt == "" {
|
||||
log.Error().Msg("JWT is empty in session")
|
||||
b.SendNew("<b>❌ Ошибка авторизации</b>\n\nПопробуйте /start", bot.Keyboard())
|
||||
b.SendNew("<b>❌ Ошибка авторизации</b>\n\nПопробуйте /start", Keyboard())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -498,8 +497,8 @@ func (s *CreativeDetails) deleteCreative(b *bot.Bot) {
|
||||
// Показываем ошибку через панель управления
|
||||
s.confirmingDelete = false // Сбрасываем флаг подтверждения
|
||||
s.ShowControlPanel(b, "<b>❌ Не удалось удалить креатив</b>\n\nПроверьте подключение и попробуйте снова.", [][]echotron.InlineKeyboardButton{
|
||||
bot.Row(bot.Button("← Назад", "back")),
|
||||
bot.Row(bot.Button("⌦ Попробовать снова", "delete_creative")),
|
||||
Row(Button("← Назад", "back")),
|
||||
Row(Button("⌦ Попробовать снова", "delete_creative")),
|
||||
})
|
||||
return
|
||||
}
|
||||
@@ -508,7 +507,7 @@ func (s *CreativeDetails) deleteCreative(b *bot.Bot) {
|
||||
|
||||
// Возвращаемся назад
|
||||
if s.BackState != nil {
|
||||
b.SendNew("✅ Креатив успешно удален!", bot.Keyboard())
|
||||
b.SendNew("✅ Креатив успешно удален!", Keyboard())
|
||||
b.SetState(s.BackState, bot.EditMessage)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -365,17 +365,17 @@ func (e *CreativeEditorFields) ShowManagementPanel(b *bot.Bot, confirmButtonText
|
||||
var buttons [][]echotron.InlineKeyboardButton
|
||||
|
||||
// Блок редактирования контента
|
||||
buttons = append(buttons, bot.Row(
|
||||
bot.Button("✎ Текст", "edit_text"),
|
||||
bot.Button("+ Медиа", "add_media"),
|
||||
buttons = append(buttons, Row(
|
||||
Button("✎ Текст", "edit_text"),
|
||||
Button("+ Медиа", "add_media"),
|
||||
))
|
||||
// Добавление кнопки сразу под основным блоком
|
||||
buttons = append(buttons, bot.Row(bot.Button("+ Добавить кнопку", "add_button")))
|
||||
buttons = append(buttons, Row(Button("+ Добавить кнопку", "add_button")))
|
||||
|
||||
// Кнопка удаления медиа (если медиа добавлено)
|
||||
if e.MediaFileID != "" {
|
||||
buttons = append(buttons, bot.Row(
|
||||
bot.Button("⌫ Убрать медиа", "delete_media"),
|
||||
buttons = append(buttons, Row(
|
||||
Button("⌫ Убрать медиа", "delete_media"),
|
||||
))
|
||||
}
|
||||
|
||||
@@ -383,7 +383,7 @@ func (e *CreativeEditorFields) ShowManagementPanel(b *bot.Bot, confirmButtonText
|
||||
if len(e.Buttons) > 0 {
|
||||
var row []echotron.InlineKeyboardButton
|
||||
for i, btn := range e.Buttons {
|
||||
row = append(row, bot.Button(
|
||||
row = append(row, Button(
|
||||
fmt.Sprintf(`⌫ Убрать «%s»`, btn.Text),
|
||||
fmt.Sprintf("delete_button:%d", i),
|
||||
))
|
||||
@@ -406,13 +406,13 @@ func (e *CreativeEditorFields) ShowManagementPanel(b *bot.Bot, confirmButtonText
|
||||
|
||||
// Финальные кнопки
|
||||
if e.Text != nil {
|
||||
buttons = append(buttons, bot.Row(
|
||||
bot.Button("← Назад", cancelCallback),
|
||||
bot.Button(confirmButtonText, confirmCallback),
|
||||
buttons = append(buttons, Row(
|
||||
Button("← Назад", cancelCallback),
|
||||
Button(confirmButtonText, confirmCallback),
|
||||
))
|
||||
} else {
|
||||
buttons = append(buttons, bot.Row(
|
||||
bot.Button("← Назад", cancelCallback),
|
||||
buttons = append(buttons, Row(
|
||||
Button("← Назад", cancelCallback),
|
||||
))
|
||||
}
|
||||
|
||||
@@ -423,7 +423,7 @@ func (e *CreativeEditorFields) ShowManagementPanel(b *bot.Bot, confirmButtonText
|
||||
func (e *CreativeEditorFields) ShowControlPanel(b *bot.Bot, text string, buttons [][]echotron.InlineKeyboardButton) {
|
||||
if e.ControlPanelMessageID == nil {
|
||||
// Отправляем новую панель
|
||||
keyboard := bot.Keyboard(buttons...)
|
||||
keyboard := Keyboard(buttons...)
|
||||
b.SendNew(text, keyboard)
|
||||
// ВАЖНО: Сохраняем копию значения, а не указатель на переменную!
|
||||
controlPanelMsgID := b.LastMessageID
|
||||
@@ -437,7 +437,7 @@ func (e *CreativeEditorFields) ShowControlPanel(b *bot.Bot, text string, buttons
|
||||
oldLastMessageID := b.LastMessageID
|
||||
b.LastMessageID = *e.ControlPanelMessageID
|
||||
|
||||
keyboard := bot.Keyboard(buttons...)
|
||||
keyboard := Keyboard(buttons...)
|
||||
b.Edit(text, keyboard)
|
||||
|
||||
// Восстанавливаем оригинальный LastMessageID
|
||||
|
||||
@@ -26,7 +26,7 @@ func (s *Creatives) Enter(b *bot.Bot, mode bot.RenderMode) {
|
||||
jwt := b.Session.JWT
|
||||
if jwt == "" {
|
||||
log.Error().Msg("JWT is empty in session")
|
||||
b.SendNew("❌ Ошибка авторизации. Попробуйте /start login", bot.Keyboard())
|
||||
b.SendNew("❌ Ошибка авторизации. Попробуйте /start login", Keyboard())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ func (s *Creatives) renderCreatives(b *bot.Bot, mode bot.RenderMode, jwt string)
|
||||
)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to get creatives")
|
||||
b.SendNew("❌ Не удалось загрузить креативы", bot.Keyboard())
|
||||
b.SendNew("❌ Не удалось загрузить креативы", Keyboard())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -87,13 +87,13 @@ func (s *Creatives) renderCreatives(b *bot.Bot, mode bot.RenderMode, jwt string)
|
||||
// Создаем слоты для креативов
|
||||
var slots []echotron.InlineKeyboardButton
|
||||
for _, creative := range page.Items {
|
||||
slots = append(slots, bot.Button(creative.Name, fmt.Sprintf("creative:%s", creative.ID)))
|
||||
slots = append(slots, Button(creative.Name, fmt.Sprintf("creative:%s", creative.ID)))
|
||||
}
|
||||
|
||||
// Если страниц >= 2, заполняем до 6 слотов пустыми кнопками
|
||||
if page.Pages >= 2 {
|
||||
for len(slots) < creativesPerPage {
|
||||
slots = append(slots, bot.Button(" ", "empty"))
|
||||
slots = append(slots, Button(" ", "empty"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,7 +107,7 @@ func (s *Creatives) renderCreatives(b *bot.Bot, mode bot.RenderMode, jwt string)
|
||||
row = append(row, slots[i+1])
|
||||
} else {
|
||||
// Если нечетное количество, добавляем пустую кнопку
|
||||
row = append(row, bot.Button(" ", "empty"))
|
||||
row = append(row, Button(" ", "empty"))
|
||||
}
|
||||
|
||||
buttons = append(buttons, row)
|
||||
@@ -117,18 +117,18 @@ func (s *Creatives) renderCreatives(b *bot.Bot, mode bot.RenderMode, jwt string)
|
||||
if navRow := ui.BuildNavigationRow(ui.PaginationConfig{
|
||||
CurrentPage: s.CurrentPage,
|
||||
TotalPages: page.Pages,
|
||||
MiddleButtons: []echotron.InlineKeyboardButton{bot.Button("+ Добавить", "add_creative")},
|
||||
MiddleButtons: []echotron.InlineKeyboardButton{Button("+ Добавить", "add_creative")},
|
||||
}); navRow != nil {
|
||||
buttons = append(buttons, navRow)
|
||||
}
|
||||
|
||||
// Нижний ряд: Назад + Архив
|
||||
buttons = append(buttons, bot.Row(
|
||||
bot.Button("← Назад", "back"),
|
||||
bot.Button("≡ Архив", "archive"),
|
||||
buttons = append(buttons, Row(
|
||||
Button("← Назад", "back"),
|
||||
Button("≡ Архив", "archive"),
|
||||
))
|
||||
|
||||
keyboard := bot.Keyboard(buttons...)
|
||||
keyboard := Keyboard(buttons...)
|
||||
b.Render(text, keyboard, mode)
|
||||
}
|
||||
|
||||
@@ -157,8 +157,8 @@ func (s *Creatives) HandleCallback(b *bot.Bot, u *echotron.Update) {
|
||||
|
||||
case data == "archive":
|
||||
// TODO: реализовать экран архива креативов
|
||||
b.Edit("📦 Архив креативов\n\nЭта функция в разработке", bot.Keyboard(
|
||||
bot.Row(bot.Button("← Назад", "back")),
|
||||
b.Edit("📦 Архив креативов\n\nЭта функция в разработке", Keyboard(
|
||||
Row(Button("← Назад", "back")),
|
||||
))
|
||||
|
||||
case data == "add_creative":
|
||||
|
||||
@@ -175,20 +175,20 @@ func (s *DateTimePicker) renderCalendarTitle() string {
|
||||
func (s *DateTimePicker) calendarKeyboard() echotron.InlineKeyboardMarkup {
|
||||
var rows [][]echotron.InlineKeyboardButton
|
||||
|
||||
rows = append(rows, bot.Row(
|
||||
rows = append(rows, Row(
|
||||
s.monthPrevButton(),
|
||||
bot.Button(monthName(s.month), dtpCallbackPrefix+"open_months"),
|
||||
bot.Button("→", dtpCallbackPrefix+"month_next"),
|
||||
Button(monthName(s.month), dtpCallbackPrefix+"open_months"),
|
||||
Button("→", dtpCallbackPrefix+"month_next"),
|
||||
))
|
||||
|
||||
rows = append(rows, bot.Row(
|
||||
bot.Button("Пн", "empty"),
|
||||
bot.Button("Вт", "empty"),
|
||||
bot.Button("Ср", "empty"),
|
||||
bot.Button("Чт", "empty"),
|
||||
bot.Button("Пт", "empty"),
|
||||
bot.Button("Сб", "empty"),
|
||||
bot.Button("Вс", "empty"),
|
||||
rows = append(rows, Row(
|
||||
Button("Пн", "empty"),
|
||||
Button("Вт", "empty"),
|
||||
Button("Ср", "empty"),
|
||||
Button("Чт", "empty"),
|
||||
Button("Пт", "empty"),
|
||||
Button("Сб", "empty"),
|
||||
Button("Вс", "empty"),
|
||||
))
|
||||
|
||||
firstOfMonth := time.Date(s.year, s.month, 1, 0, 0, 0, 0, mskLocation)
|
||||
@@ -202,16 +202,16 @@ func (s *DateTimePicker) calendarKeyboard() echotron.InlineKeyboardMarkup {
|
||||
|
||||
var row []echotron.InlineKeyboardButton
|
||||
for i := 1; i < weekday; i++ {
|
||||
row = append(row, bot.Button(" ", "empty"))
|
||||
row = append(row, Button(" ", "empty"))
|
||||
}
|
||||
|
||||
for day := 1; day <= daysInMonth; day++ {
|
||||
date := time.Date(s.year, s.month, day, 0, 0, 0, 0, mskLocation)
|
||||
if date.Before(todayDate) {
|
||||
row = append(row, bot.Button("-", "empty"))
|
||||
row = append(row, Button("-", "empty"))
|
||||
} else {
|
||||
label := fmt.Sprintf("%d", day)
|
||||
row = append(row, bot.Button(label, fmt.Sprintf("%sday:%04d-%02d-%02d", dtpCallbackPrefix, s.year, int(s.month), day)))
|
||||
row = append(row, Button(label, fmt.Sprintf("%sday:%04d-%02d-%02d", dtpCallbackPrefix, s.year, int(s.month), day)))
|
||||
}
|
||||
if len(row) == 7 {
|
||||
rows = append(rows, row)
|
||||
@@ -221,17 +221,17 @@ func (s *DateTimePicker) calendarKeyboard() echotron.InlineKeyboardMarkup {
|
||||
|
||||
if len(row) > 0 {
|
||||
for len(row) < 7 {
|
||||
row = append(row, bot.Button(" ", "empty"))
|
||||
row = append(row, Button(" ", "empty"))
|
||||
}
|
||||
rows = append(rows, row)
|
||||
}
|
||||
|
||||
rows = append(rows, bot.Row(
|
||||
bot.Button("← Назад", "back"),
|
||||
bot.Button("Отмена", "cancel"),
|
||||
rows = append(rows, Row(
|
||||
Button("← Назад", "back"),
|
||||
Button("Отмена", "cancel"),
|
||||
))
|
||||
|
||||
return bot.Keyboard(rows...)
|
||||
return Keyboard(rows...)
|
||||
}
|
||||
|
||||
func (s *DateTimePicker) monthsKeyboard() echotron.InlineKeyboardMarkup {
|
||||
@@ -239,25 +239,25 @@ func (s *DateTimePicker) monthsKeyboard() echotron.InlineKeyboardMarkup {
|
||||
|
||||
months := []string{"Янв", "Фев", "Мар", "Апр", "Май", "Июн", "Июл", "Авг", "Сен", "Окт", "Ноя", "Дек"}
|
||||
for i := 0; i < 12; i += 3 {
|
||||
rows = append(rows, bot.Row(
|
||||
bot.Button(months[i], fmt.Sprintf("%smonth:%d", dtpCallbackPrefix, i+1)),
|
||||
bot.Button(months[i+1], fmt.Sprintf("%smonth:%d", dtpCallbackPrefix, i+2)),
|
||||
bot.Button(months[i+2], fmt.Sprintf("%smonth:%d", dtpCallbackPrefix, i+3)),
|
||||
rows = append(rows, Row(
|
||||
Button(months[i], fmt.Sprintf("%smonth:%d", dtpCallbackPrefix, i+1)),
|
||||
Button(months[i+1], fmt.Sprintf("%smonth:%d", dtpCallbackPrefix, i+2)),
|
||||
Button(months[i+2], fmt.Sprintf("%smonth:%d", dtpCallbackPrefix, i+3)),
|
||||
))
|
||||
}
|
||||
|
||||
rows = append(rows, bot.Row(
|
||||
bot.Button("← Назад", "back"),
|
||||
rows = append(rows, Row(
|
||||
Button("← Назад", "back"),
|
||||
))
|
||||
|
||||
return bot.Keyboard(rows...)
|
||||
return Keyboard(rows...)
|
||||
}
|
||||
|
||||
func (s *DateTimePicker) timeCombinedKeyboard() echotron.InlineKeyboardMarkup {
|
||||
var rows [][]echotron.InlineKeyboardButton
|
||||
rows = append(rows, bot.Row(bot.Button("Часы", "empty")))
|
||||
rows = append(rows, Row(Button("Часы", "empty")))
|
||||
for i := 0; i < 24; i += 6 {
|
||||
rows = append(rows, bot.Row(
|
||||
rows = append(rows, Row(
|
||||
s.hourButton(i),
|
||||
s.hourButton(i+1),
|
||||
s.hourButton(i+2),
|
||||
@@ -266,10 +266,10 @@ func (s *DateTimePicker) timeCombinedKeyboard() echotron.InlineKeyboardMarkup {
|
||||
s.hourButton(i+5),
|
||||
))
|
||||
}
|
||||
rows = append(rows, bot.Row(bot.Button("Минуты", "empty")))
|
||||
rows = append(rows, Row(Button("Минуты", "empty")))
|
||||
minutes := []int{0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55}
|
||||
for i := 0; i < len(minutes); i += 6 {
|
||||
rows = append(rows, bot.Row(
|
||||
rows = append(rows, Row(
|
||||
s.minuteButton(minutes[i]),
|
||||
s.minuteButton(minutes[i+1]),
|
||||
s.minuteButton(minutes[i+2]),
|
||||
@@ -278,18 +278,18 @@ func (s *DateTimePicker) timeCombinedKeyboard() echotron.InlineKeyboardMarkup {
|
||||
s.minuteButton(minutes[i+5]),
|
||||
))
|
||||
}
|
||||
rows = append(rows, bot.Row(
|
||||
bot.Button("← Назад", "back"),
|
||||
bot.Button("Готово", dtpCallbackPrefix+"confirm"),
|
||||
rows = append(rows, Row(
|
||||
Button("← Назад", "back"),
|
||||
Button("Готово", dtpCallbackPrefix+"confirm"),
|
||||
))
|
||||
return bot.Keyboard(rows...)
|
||||
return Keyboard(rows...)
|
||||
}
|
||||
|
||||
func (s *DateTimePicker) confirmKeyboard() echotron.InlineKeyboardMarkup {
|
||||
return bot.Keyboard(
|
||||
bot.Row(
|
||||
bot.Button("← Назад", "back"),
|
||||
bot.Button("Готово", dtpCallbackPrefix+"confirm"),
|
||||
return Keyboard(
|
||||
Row(
|
||||
Button("← Назад", "back"),
|
||||
Button("Готово", dtpCallbackPrefix+"confirm"),
|
||||
),
|
||||
)
|
||||
}
|
||||
@@ -426,23 +426,23 @@ func (s *DateTimePicker) confirmSelection(b *bot.Bot) {
|
||||
func (s *DateTimePicker) hourButton(hour int) echotron.InlineKeyboardButton {
|
||||
label := fmt.Sprintf("%02d", hour)
|
||||
if s.isHourDisabled(hour) {
|
||||
return bot.Button("-", "empty")
|
||||
return Button("-", "empty")
|
||||
}
|
||||
if s.selectedHour != nil && *s.selectedHour == hour {
|
||||
label = "● " + label
|
||||
}
|
||||
return bot.Button(label, fmt.Sprintf("%shour:%02d", dtpCallbackPrefix, hour))
|
||||
return Button(label, fmt.Sprintf("%shour:%02d", dtpCallbackPrefix, hour))
|
||||
}
|
||||
|
||||
func (s *DateTimePicker) minuteButton(minute int) echotron.InlineKeyboardButton {
|
||||
label := fmt.Sprintf("%02d", minute)
|
||||
if s.isMinuteDisabled(minute) {
|
||||
return bot.Button("-", "empty")
|
||||
return Button("-", "empty")
|
||||
}
|
||||
if s.selectedMinute != nil && *s.selectedMinute == minute {
|
||||
label = "● " + label
|
||||
}
|
||||
return bot.Button(label, fmt.Sprintf("%smin:%02d", dtpCallbackPrefix, minute))
|
||||
return Button(label, fmt.Sprintf("%smin:%02d", dtpCallbackPrefix, minute))
|
||||
}
|
||||
|
||||
func (s *DateTimePicker) isHourDisabled(hour int) bool {
|
||||
@@ -499,9 +499,9 @@ func (s *DateTimePicker) nextMonth() {
|
||||
func (s *DateTimePicker) monthPrevButton() echotron.InlineKeyboardButton {
|
||||
now := time.Now().In(mskLocation)
|
||||
if s.year == now.Year() && s.month == now.Month() {
|
||||
return bot.Button("-", "empty")
|
||||
return Button("-", "empty")
|
||||
}
|
||||
return bot.Button("←", dtpCallbackPrefix+"month_prev")
|
||||
return Button("←", dtpCallbackPrefix+"month_prev")
|
||||
}
|
||||
|
||||
func daysInMonth(year int, month time.Month) int {
|
||||
|
||||
34
telegram_bot/screens/helpers.go
Normal file
34
telegram_bot/screens/helpers.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package screens
|
||||
|
||||
import (
|
||||
"github.com/NicoNex/echotron/v3"
|
||||
)
|
||||
|
||||
var emptyKeyboard = Keyboard()
|
||||
|
||||
func Button(text, data string) echotron.InlineKeyboardButton {
|
||||
return echotron.InlineKeyboardButton{
|
||||
Text: text,
|
||||
CallbackData: data,
|
||||
}
|
||||
}
|
||||
|
||||
func URLButton(text, url string) echotron.InlineKeyboardButton {
|
||||
return echotron.InlineKeyboardButton{
|
||||
Text: text,
|
||||
URL: url,
|
||||
}
|
||||
}
|
||||
|
||||
func Row(buttons ...echotron.InlineKeyboardButton) []echotron.InlineKeyboardButton { return buttons }
|
||||
|
||||
func Keyboard(rows ...[]echotron.InlineKeyboardButton) echotron.InlineKeyboardMarkup {
|
||||
if len(rows) == 0 {
|
||||
return echotron.InlineKeyboardMarkup{
|
||||
InlineKeyboard: [][]echotron.InlineKeyboardButton{},
|
||||
}
|
||||
}
|
||||
return echotron.InlineKeyboardMarkup{
|
||||
InlineKeyboard: rows,
|
||||
}
|
||||
}
|
||||
@@ -21,14 +21,14 @@ Smartpost — платформа для покупки и продажи рек
|
||||
`
|
||||
|
||||
func (s *MainMenu) Enter(b *bot.Bot, mode bot.RenderMode) {
|
||||
keyboard := bot.Keyboard(
|
||||
bot.Row(
|
||||
bot.Button("Мои проекты", "my_projects"),
|
||||
bot.URLButton("Поддержка", "https://example.com"),
|
||||
keyboard := Keyboard(
|
||||
Row(
|
||||
Button("Мои проекты", "my_projects"),
|
||||
URLButton("Поддержка", "https://example.com"),
|
||||
),
|
||||
bot.Row(
|
||||
bot.Button("Закупы", "purchases"),
|
||||
bot.URLButton("Дашборд", "https://example.com"),
|
||||
Row(
|
||||
Button("Закупы", "purchases"),
|
||||
URLButton("Дашборд", "https://example.com"),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ func (s *MyProjects) Enter(b *bot.Bot, mode bot.RenderMode) {
|
||||
jwt := b.Session.JWT
|
||||
if jwt == "" {
|
||||
log.Error().Msg("JWT is empty in session")
|
||||
b.SendNew("❌ Ошибка авторизации. Попробуйте /start login", bot.Keyboard())
|
||||
b.SendNew("❌ Ошибка авторизации. Попробуйте /start login", Keyboard())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ func (s *MyProjects) Enter(b *bot.Bot, mode bot.RenderMode) {
|
||||
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())
|
||||
b.SendNew("❌ Не удалось загрузить workspace'ы", Keyboard())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ func (s *MyProjects) Enter(b *bot.Bot, mode bot.RenderMode) {
|
||||
workspace, err := b.Backend.CreateWorkspace(context.Background(), jwt, username)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to create workspace")
|
||||
b.SendNew("❌ Не удалось создать workspace", bot.Keyboard())
|
||||
b.SendNew("❌ Не удалось создать workspace", Keyboard())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ 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())
|
||||
b.SendNew("❌ Не удалось загрузить проекты", Keyboard())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -122,13 +122,13 @@ func (s *MyProjects) renderProjects(b *bot.Bot, mode bot.RenderMode, jwt string)
|
||||
// Создаем слоты для проектов
|
||||
var slots []echotron.InlineKeyboardButton
|
||||
for _, project := range page.Items {
|
||||
slots = append(slots, bot.Button(project.Title, fmt.Sprintf("project:%s", project.ID)))
|
||||
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, bot.Button(" ", "empty"))
|
||||
slots = append(slots, Button(" ", "empty"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,7 +142,7 @@ func (s *MyProjects) renderProjects(b *bot.Bot, mode bot.RenderMode, jwt string)
|
||||
row = append(row, slots[i+1])
|
||||
} else {
|
||||
// Если нечетное количество, добавляем пустую кнопку
|
||||
row = append(row, bot.Button(" ", "empty"))
|
||||
row = append(row, Button(" ", "empty"))
|
||||
}
|
||||
|
||||
buttons = append(buttons, row)
|
||||
@@ -152,18 +152,18 @@ func (s *MyProjects) renderProjects(b *bot.Bot, mode bot.RenderMode, jwt string)
|
||||
if navRow := ui.BuildNavigationRow(ui.PaginationConfig{
|
||||
CurrentPage: s.CurrentPage,
|
||||
TotalPages: page.Pages,
|
||||
MiddleButtons: []echotron.InlineKeyboardButton{bot.Button("+ Добавить", "add_project")},
|
||||
MiddleButtons: []echotron.InlineKeyboardButton{Button("+ Добавить", "add_project")},
|
||||
}); navRow != nil {
|
||||
buttons = append(buttons, navRow)
|
||||
}
|
||||
|
||||
// Нижний ряд: Назад + Архив
|
||||
buttons = append(buttons, bot.Row(
|
||||
bot.Button("← Назад", "menu"),
|
||||
bot.Button("≡ Архив", "archive"),
|
||||
buttons = append(buttons, Row(
|
||||
Button("← Назад", "menu"),
|
||||
Button("≡ Архив", "archive"),
|
||||
))
|
||||
|
||||
keyboard := bot.Keyboard(buttons...)
|
||||
keyboard := Keyboard(buttons...)
|
||||
b.Render(text, keyboard, mode)
|
||||
}
|
||||
|
||||
@@ -189,8 +189,8 @@ func (s *MyProjects) HandleCallback(b *bot.Bot, u *echotron.Update) {
|
||||
b.SetState(&MainMenu{}, bot.EditMessage)
|
||||
|
||||
case data == "archive":
|
||||
b.Edit("📦 Архив проектов\n\nЭта функция в разработке", bot.Keyboard(
|
||||
bot.Row(bot.Button("← Назад", "back_to_projects")),
|
||||
b.Edit("📦 Архив проектов\n\nЭта функция в разработке", Keyboard(
|
||||
Row(Button("← Назад", "back_to_projects")),
|
||||
))
|
||||
|
||||
case data == "add_project":
|
||||
|
||||
@@ -20,8 +20,8 @@ type ProjectDetails struct {
|
||||
|
||||
func (s *ProjectDetails) Enter(b *bot.Bot, mode bot.RenderMode) {
|
||||
if s.Project == nil {
|
||||
b.SendNew("❌ Проект не найден", bot.Keyboard(
|
||||
bot.Row(bot.Button("← Назад", "back_to_projects")),
|
||||
b.SendNew("❌ Проект не найден", Keyboard(
|
||||
Row(Button("← Назад", "back_to_projects")),
|
||||
))
|
||||
return
|
||||
}
|
||||
@@ -76,9 +76,9 @@ func (s *ProjectDetails) buildKeyboard() echotron.InlineKeyboardMarkup {
|
||||
var buttons [][]echotron.InlineKeyboardButton
|
||||
|
||||
// Первый ряд: Креативы и Закупы
|
||||
buttons = append(buttons, bot.Row(
|
||||
bot.Button("Креативы", fmt.Sprintf("creatives:%s", s.Project.ID)),
|
||||
bot.Button("Закупы", fmt.Sprintf("purchases:%s", s.Project.ID)),
|
||||
buttons = append(buttons, Row(
|
||||
Button("Креативы", fmt.Sprintf("creatives:%s", s.Project.ID)),
|
||||
Button("Закупы", fmt.Sprintf("purchases:%s", s.Project.ID)),
|
||||
))
|
||||
|
||||
// Второй ряд: Тип вступления по умолчанию для закупов (с подтверждением)
|
||||
@@ -102,17 +102,17 @@ func (s *ProjectDetails) buildKeyboard() echotron.InlineKeyboardMarkup {
|
||||
}
|
||||
linkTypeButtonText = fmt.Sprintf("Вступление: %s", currentType)
|
||||
}
|
||||
buttons = append(buttons, bot.Row(
|
||||
bot.Button(linkTypeButtonText, fmt.Sprintf("link_type:%s", s.Project.ID)),
|
||||
buttons = append(buttons, Row(
|
||||
Button(linkTypeButtonText, fmt.Sprintf("link_type:%s", s.Project.ID)),
|
||||
))
|
||||
|
||||
// Нижний ряд: Назад и Архивировать
|
||||
buttons = append(buttons, bot.Row(
|
||||
bot.Button("← Назад", "back_to_projects"),
|
||||
bot.Button("≡ Архивировать", fmt.Sprintf("archive:%s", s.Project.ID)),
|
||||
buttons = append(buttons, Row(
|
||||
Button("← Назад", "back_to_projects"),
|
||||
Button("≡ Архивировать", fmt.Sprintf("archive:%s", s.Project.ID)),
|
||||
))
|
||||
|
||||
return bot.Keyboard(buttons...)
|
||||
return Keyboard(buttons...)
|
||||
}
|
||||
|
||||
func (s *ProjectDetails) HandleCallback(b *bot.Bot, u *echotron.Update) {
|
||||
@@ -181,8 +181,8 @@ func (s *ProjectDetails) HandleCallback(b *bot.Bot, u *echotron.Update) {
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
b.SendNew(fmt.Sprintf("❌ Ошибка при изменении типа ссылки: %v", err), bot.Keyboard(
|
||||
bot.Row(bot.Button("← Назад", "back_to_project")),
|
||||
b.SendNew(fmt.Sprintf("❌ Ошибка при изменении типа ссылки: %v", err), Keyboard(
|
||||
Row(Button("← Назад", "back_to_project")),
|
||||
))
|
||||
return
|
||||
}
|
||||
@@ -195,8 +195,8 @@ func (s *ProjectDetails) HandleCallback(b *bot.Bot, u *echotron.Update) {
|
||||
|
||||
default:
|
||||
// Для остальных кнопок показываем заглушку
|
||||
b.Edit(fmt.Sprintf("🚧 Функция в разработке\n\nCallback: %s", data), bot.Keyboard(
|
||||
bot.Row(bot.Button("← Назад", "back_to_project")),
|
||||
b.Edit(fmt.Sprintf("🚧 Функция в разработке\n\nCallback: %s", data), Keyboard(
|
||||
Row(Button("← Назад", "back_to_project")),
|
||||
))
|
||||
}
|
||||
return
|
||||
|
||||
@@ -23,7 +23,7 @@ func (s *PurchaseDetails) Enter(b *bot.Bot, mode bot.RenderMode) {
|
||||
jwt := b.Session.JWT
|
||||
if jwt == "" {
|
||||
log.Error().Msg("JWT is empty in session")
|
||||
b.SendNew("❌ Ошибка авторизации. Попробуйте /start", bot.Keyboard())
|
||||
b.SendNew("❌ Ошибка авторизации. Попробуйте /start", Keyboard())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ func (s *PurchaseDetails) renderDetails(b *bot.Bot, mode bot.RenderMode, jwt str
|
||||
)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to get purchase")
|
||||
b.SendNew("❌ Не удалось загрузить закуп", bot.Keyboard())
|
||||
b.SendNew("❌ Не удалось загрузить закуп", Keyboard())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -153,11 +153,11 @@ func (s *PurchaseDetails) renderDetails(b *bot.Bot, mode bot.RenderMode, jwt str
|
||||
var buttons [][]echotron.InlineKeyboardButton
|
||||
|
||||
// Кнопка назад
|
||||
buttons = append(buttons, bot.Row(
|
||||
bot.Button("← Назад", "back"),
|
||||
buttons = append(buttons, Row(
|
||||
Button("← Назад", "back"),
|
||||
))
|
||||
|
||||
keyboard := bot.Keyboard(buttons...)
|
||||
keyboard := Keyboard(buttons...)
|
||||
b.Render(text, keyboard, mode)
|
||||
}
|
||||
|
||||
|
||||
@@ -47,6 +47,7 @@ type PurchaseOptionalDetails struct {
|
||||
CostCopy *CostEntry
|
||||
CostBeforeCopy *float64
|
||||
FormatCopy *string
|
||||
ChannelEditMode string // "edit" или "copy"
|
||||
BackState bot.State
|
||||
}
|
||||
|
||||
@@ -58,6 +59,10 @@ type CostEntry struct {
|
||||
func (s *PurchaseOptionalDetails) Enter(b *bot.Bot, mode bot.RenderMode) {
|
||||
s.ensureDefaults()
|
||||
if s.InputMode != "" {
|
||||
if s.InputMode == "mode_select" {
|
||||
s.renderModeSelect(b, mode)
|
||||
return
|
||||
}
|
||||
if s.renderParamScreens(b, mode) {
|
||||
return
|
||||
}
|
||||
@@ -72,30 +77,36 @@ func (s *PurchaseOptionalDetails) Enter(b *bot.Bot, mode bot.RenderMode) {
|
||||
text += s.formatOptionalSummary()
|
||||
|
||||
var rows [][]echotron.InlineKeyboardButton
|
||||
rows = append(rows, bot.Row(
|
||||
rows = append(rows, Row(
|
||||
s.placementButton(),
|
||||
s.costButton(),
|
||||
))
|
||||
rows = append(rows, bot.Row(
|
||||
rows = append(rows, Row(
|
||||
s.paymentButton(),
|
||||
s.typeButton(),
|
||||
))
|
||||
rows = append(rows, bot.Row(
|
||||
rows = append(rows, Row(
|
||||
s.costBeforeButton(),
|
||||
s.formatButton(),
|
||||
))
|
||||
rows = append(rows, bot.Row(
|
||||
rows = append(rows, Row(
|
||||
s.commentButton(),
|
||||
))
|
||||
|
||||
// Кнопка переключения режима (только если несколько каналов)
|
||||
if len(s.Channels) > 1 {
|
||||
rows = append(rows, Row(s.globalModeButton()))
|
||||
}
|
||||
|
||||
if row := s.costBeforeDeleteRow(); row != nil {
|
||||
rows = append(rows, row)
|
||||
}
|
||||
if row := s.commentDeleteRow(); row != nil {
|
||||
rows = append(rows, row)
|
||||
}
|
||||
rows = append(rows, bot.Row(bot.Button("Назад", "back"), bot.Button("Далее", "next")))
|
||||
rows = append(rows, Row(Button("Назад", "back"), Button("Далее", "next")))
|
||||
|
||||
keyboard := bot.Keyboard(rows...)
|
||||
keyboard := Keyboard(rows...)
|
||||
|
||||
b.Render(text, keyboard, mode)
|
||||
}
|
||||
@@ -108,8 +119,9 @@ func (s *PurchaseOptionalDetails) HandleCallback(b *bot.Bot, u *echotron.Update)
|
||||
|
||||
switch u.CallbackQuery.Data {
|
||||
case "opt_datetime":
|
||||
if len(s.Channels) > 1 {
|
||||
s.InputMode = "placement_scope"
|
||||
// Открываем редактор в текущем режиме
|
||||
if s.PlacementMode == "per_channel" && len(s.Channels) > 1 {
|
||||
s.InputMode = "placement_channels"
|
||||
s.Enter(b, bot.EditMessage)
|
||||
} else {
|
||||
s.setParamMode("placement", "common")
|
||||
@@ -126,8 +138,9 @@ func (s *PurchaseOptionalDetails) HandleCallback(b *bot.Bot, u *echotron.Update)
|
||||
}), bot.EditMessage)
|
||||
|
||||
case "opt_cost":
|
||||
if len(s.Channels) > 1 {
|
||||
s.InputMode = "cost_scope"
|
||||
// Открываем редактор в текущем режиме
|
||||
if s.CostMode == "per_channel" && len(s.Channels) > 1 {
|
||||
s.InputMode = "cost_channels"
|
||||
s.Enter(b, bot.EditMessage)
|
||||
} else {
|
||||
s.setParamMode("cost", "common")
|
||||
@@ -139,8 +152,9 @@ func (s *PurchaseOptionalDetails) HandleCallback(b *bot.Bot, u *echotron.Update)
|
||||
s.Enter(b, bot.EditMessage)
|
||||
|
||||
case "opt_format":
|
||||
if len(s.Channels) > 1 {
|
||||
s.InputMode = "format_scope"
|
||||
// Открываем редактор в текущем режиме
|
||||
if s.FormatMode == "per_channel" && len(s.Channels) > 1 {
|
||||
s.InputMode = "format_channels"
|
||||
s.Enter(b, bot.EditMessage)
|
||||
} else {
|
||||
s.setParamMode("format", "common")
|
||||
@@ -168,8 +182,9 @@ func (s *PurchaseOptionalDetails) HandleCallback(b *bot.Bot, u *echotron.Update)
|
||||
s.Enter(b, bot.EditMessage)
|
||||
|
||||
case "cost_before":
|
||||
if len(s.Channels) > 1 {
|
||||
s.InputMode = "cost_before_scope"
|
||||
// Открываем редактор в текущем режиме
|
||||
if s.CostBeforeMode == "per_channel" && len(s.Channels) > 1 {
|
||||
s.InputMode = "cost_before_channels"
|
||||
s.Enter(b, bot.EditMessage)
|
||||
} else {
|
||||
s.setParamMode("cost_before", "common")
|
||||
@@ -210,7 +225,7 @@ func (s *PurchaseOptionalDetails) HandleCallback(b *bot.Bot, u *echotron.Update)
|
||||
jwt := b.Session.JWT
|
||||
if jwt == "" {
|
||||
log.Error().Msg("JWT is empty in session")
|
||||
b.SendNew("❌ Ошибка авторизации. Попробуйте /start", bot.Keyboard())
|
||||
b.SendNew("❌ Ошибка авторизации. Попробуйте /start", Keyboard())
|
||||
return
|
||||
}
|
||||
s.createPurchase(b, jwt)
|
||||
@@ -219,9 +234,57 @@ func (s *PurchaseOptionalDetails) HandleCallback(b *bot.Bot, u *echotron.Update)
|
||||
b.SetState(s.BackState, bot.EditMessage)
|
||||
}
|
||||
default:
|
||||
if u.CallbackQuery.Data == "noop" {
|
||||
// Пустая кнопка - ничего не делаем
|
||||
return
|
||||
}
|
||||
if s.handleParamCallback(b, u.CallbackQuery.Data) {
|
||||
return
|
||||
}
|
||||
if u.CallbackQuery.Data == "toggle_global_mode" {
|
||||
// Открываем меню выбора параметра для переключения режима
|
||||
s.InputMode = "mode_select"
|
||||
s.Enter(b, bot.EditMessage)
|
||||
return
|
||||
}
|
||||
if strings.HasPrefix(u.CallbackQuery.Data, "channel_mode:") {
|
||||
// Переключаем режим редактирования каналов
|
||||
mode := strings.TrimPrefix(u.CallbackQuery.Data, "channel_mode:")
|
||||
s.ChannelEditMode = mode
|
||||
s.Enter(b, bot.EditMessage)
|
||||
return
|
||||
}
|
||||
if strings.HasPrefix(u.CallbackQuery.Data, "toggle_param_mode:") {
|
||||
// Переключаем режим конкретного параметра
|
||||
param := strings.TrimPrefix(u.CallbackQuery.Data, "toggle_param_mode:")
|
||||
currentMode := s.paramMode(param)
|
||||
if currentMode == "common" {
|
||||
s.setParamMode(param, "per_channel")
|
||||
} else {
|
||||
s.setParamMode(param, "common")
|
||||
}
|
||||
// Остаемся в меню выбора режима
|
||||
s.Enter(b, bot.EditMessage)
|
||||
return
|
||||
}
|
||||
if strings.HasPrefix(u.CallbackQuery.Data, "type:") {
|
||||
value := strings.TrimPrefix(u.CallbackQuery.Data, "type:")
|
||||
if value == "custom" {
|
||||
s.InputMode = "type_custom"
|
||||
s.Enter(b, bot.EditMessage)
|
||||
return
|
||||
}
|
||||
// Нормализуем значение для БД
|
||||
switch value {
|
||||
case "self_promo":
|
||||
s.PurchaseType = "самопиар"
|
||||
case "standard":
|
||||
s.PurchaseType = "стандарт"
|
||||
}
|
||||
s.InputMode = ""
|
||||
s.Enter(b, bot.EditMessage)
|
||||
return
|
||||
}
|
||||
if strings.HasPrefix(u.CallbackQuery.Data, "format:") {
|
||||
value := strings.TrimPrefix(u.CallbackQuery.Data, "format:")
|
||||
s.setFormatValue(value)
|
||||
@@ -251,7 +314,7 @@ func (s *PurchaseOptionalDetails) HandleMessage(b *bot.Bot, u *echotron.Update)
|
||||
}
|
||||
|
||||
switch s.InputMode {
|
||||
case "type":
|
||||
case "type_custom":
|
||||
s.PurchaseType = text
|
||||
case "format_custom":
|
||||
s.setFormatValue(text)
|
||||
@@ -350,87 +413,104 @@ func (s *PurchaseOptionalDetails) costTypeLabel() string {
|
||||
}
|
||||
|
||||
func (s *PurchaseOptionalDetails) placementButton() echotron.InlineKeyboardButton {
|
||||
if !s.hasPlacementValue() {
|
||||
return bot.Button("+ Дата размещения", "opt_datetime")
|
||||
icon := "+"
|
||||
if s.hasPlacementValue() {
|
||||
icon = "✎"
|
||||
}
|
||||
return bot.Button("✎ Дата размещения", "opt_datetime")
|
||||
modeIcon := ""
|
||||
if len(s.Channels) > 1 && s.PlacementMode == "per_channel" {
|
||||
modeIcon = " 👥"
|
||||
}
|
||||
return Button(fmt.Sprintf("%s Дата размещения%s", icon, modeIcon), "opt_datetime")
|
||||
}
|
||||
|
||||
func (s *PurchaseOptionalDetails) paymentButton() echotron.InlineKeyboardButton {
|
||||
if s.PaymentDate == nil {
|
||||
return bot.Button("+ Дата оплаты", "opt_payment_date")
|
||||
return Button("+ Дата оплаты", "opt_payment_date")
|
||||
}
|
||||
return bot.Button("✎ Дата оплаты", "opt_payment_date")
|
||||
return Button("✎ Дата оплаты", "opt_payment_date")
|
||||
}
|
||||
|
||||
func (s *PurchaseOptionalDetails) costButton() echotron.InlineKeyboardButton {
|
||||
if !s.hasCostValue() {
|
||||
return bot.Button("+ Стоимость", "opt_cost")
|
||||
icon := "+"
|
||||
if s.hasCostValue() {
|
||||
icon = "✎"
|
||||
}
|
||||
return bot.Button("✎ Стоимость", "opt_cost")
|
||||
modeIcon := ""
|
||||
if len(s.Channels) > 1 && s.CostMode == "per_channel" {
|
||||
modeIcon = " 👥"
|
||||
}
|
||||
return Button(fmt.Sprintf("%s Стоимость%s", icon, modeIcon), "opt_cost")
|
||||
}
|
||||
|
||||
func (s *PurchaseOptionalDetails) costBeforeButton() echotron.InlineKeyboardButton {
|
||||
if !s.hasCostBeforeValue() {
|
||||
return bot.Button("+ До торга", "cost_before")
|
||||
icon := "+"
|
||||
if s.hasCostBeforeValue() {
|
||||
icon = "✎"
|
||||
}
|
||||
return bot.Button("✎ До торга", "cost_before")
|
||||
modeIcon := ""
|
||||
if len(s.Channels) > 1 && s.CostBeforeMode == "per_channel" {
|
||||
modeIcon = " 👥"
|
||||
}
|
||||
return Button(fmt.Sprintf("%s До торга%s", icon, modeIcon), "cost_before")
|
||||
}
|
||||
|
||||
func (s *PurchaseOptionalDetails) typeButton() echotron.InlineKeyboardButton {
|
||||
if s.PurchaseType == "" {
|
||||
return bot.Button("+ Тип", "opt_type")
|
||||
return Button("+ Тип", "opt_type")
|
||||
}
|
||||
return bot.Button("✎ Тип", "opt_type")
|
||||
return Button("✎ Тип", "opt_type")
|
||||
}
|
||||
|
||||
func (s *PurchaseOptionalDetails) formatButton() echotron.InlineKeyboardButton {
|
||||
if !s.hasFormatValue() {
|
||||
return bot.Button("+ Формат", "opt_format")
|
||||
icon := "+"
|
||||
if s.hasFormatValue() {
|
||||
icon = "✎"
|
||||
}
|
||||
return bot.Button("✎ Формат", "opt_format")
|
||||
modeIcon := ""
|
||||
if len(s.Channels) > 1 && s.FormatMode == "per_channel" {
|
||||
modeIcon = " 👥"
|
||||
}
|
||||
return Button(fmt.Sprintf("%s Формат%s", icon, modeIcon), "opt_format")
|
||||
}
|
||||
|
||||
func (s *PurchaseOptionalDetails) commentButton() echotron.InlineKeyboardButton {
|
||||
if s.Comment == "" {
|
||||
return bot.Button("+ Комментарий", "opt_comment")
|
||||
return Button("+ Комментарий", "opt_comment")
|
||||
}
|
||||
return bot.Button("✎ Комментарий", "opt_comment")
|
||||
return Button("✎ Комментарий", "opt_comment")
|
||||
}
|
||||
|
||||
func (s *PurchaseOptionalDetails) globalModeButton() echotron.InlineKeyboardButton {
|
||||
return Button("⚙ Общие / По каналам", "toggle_global_mode")
|
||||
}
|
||||
|
||||
func (s *PurchaseOptionalDetails) commentDeleteRow() []echotron.InlineKeyboardButton {
|
||||
if s.Comment == "" {
|
||||
return nil
|
||||
}
|
||||
return bot.Row(bot.Button("⌫ Удалить комментарий", "delete_comment"))
|
||||
return Row(Button("⌫ Удалить комментарий", "delete_comment"))
|
||||
}
|
||||
|
||||
func (s *PurchaseOptionalDetails) costBeforeDeleteRow() []echotron.InlineKeyboardButton {
|
||||
if s.CostBeforeBargain == nil || s.CostBeforeMode == "per_channel" {
|
||||
return nil
|
||||
}
|
||||
return bot.Row(bot.Button("⌫ Удалить до торга", "delete_cost_before"))
|
||||
return Row(Button("⌫ Удалить до торга", "delete_cost_before"))
|
||||
}
|
||||
|
||||
func (s *PurchaseOptionalDetails) renderInputPrompt(b *bot.Bot, mode bot.RenderMode) {
|
||||
text := "<i>… › Создание › Дополнительно</i>\n\n"
|
||||
switch s.InputMode {
|
||||
case "type":
|
||||
text += "<b>Введите тип закупа</b>\n\nНапример: <code>вп</code> или <code>стандарт</code>"
|
||||
case "cost_select":
|
||||
text = "<i>Страница ввода стоимости</i>\n\n"
|
||||
if s.CurrentChannel != "" {
|
||||
text += fmt.Sprintf("Канал: <b>@%s</b>\n\n", s.CurrentChannel)
|
||||
}
|
||||
text += "Ставка СРМ / Фикс цена (по умолчанию фикс)\n\n"
|
||||
costValue := s.formatCostValue()
|
||||
if s.CurrentChannel != "" {
|
||||
costValue = s.formatCostForChannel(s.CurrentChannel)
|
||||
}
|
||||
keyboard := bot.Keyboard(
|
||||
bot.Row(bot.Button(fmt.Sprintf("Тип: %s", s.costTypeLabelForCurrent()), "cost_type_toggle")),
|
||||
bot.Row(bot.Button(fmt.Sprintf("Стоимость: %s", costValue), "cost_value")),
|
||||
bot.Row(bot.Button("Назад", s.backAction())),
|
||||
text += "<b>Выберите тип закупа</b>"
|
||||
keyboard := Keyboard(
|
||||
Row(
|
||||
Button("Самопиар", "type:self_promo"),
|
||||
Button("Стандарт", "type:standard"),
|
||||
),
|
||||
Row(Button("Другое", "type:custom")),
|
||||
Row(Button("← Назад", "back_to_optional")),
|
||||
)
|
||||
if mode == bot.EditMessage {
|
||||
b.Edit(text, keyboard)
|
||||
@@ -438,11 +518,26 @@ func (s *PurchaseOptionalDetails) renderInputPrompt(b *bot.Bot, mode bot.RenderM
|
||||
b.SendNew(text, keyboard)
|
||||
}
|
||||
return
|
||||
case "type_custom":
|
||||
text += "<b>Введите тип закупа</b>\n\nНапример: <code>вп</code>"
|
||||
case "cost_value":
|
||||
text += "<b>Ввод стоимости (СРМ / фикс)</b>\n\nНапример: <code>15000</code>"
|
||||
text += "<b>Ввод стоимости</b>\n\nНапример: <code>15000</code>"
|
||||
if s.CurrentChannel != "" {
|
||||
text += fmt.Sprintf("\n\nКанал: <b>@%s</b>", s.CurrentChannel)
|
||||
}
|
||||
|
||||
typeLabel := s.costTypeLabelForCurrent()
|
||||
keyboard := Keyboard(
|
||||
Row(Button(fmt.Sprintf("Тип: %s", typeLabel), "cost_type_toggle")),
|
||||
Row(Button("← Назад", s.backAction())),
|
||||
)
|
||||
|
||||
if mode == bot.EditMessage {
|
||||
b.Edit(text, keyboard)
|
||||
} else {
|
||||
b.SendNew(text, keyboard)
|
||||
}
|
||||
return
|
||||
case "cost_before_value":
|
||||
text += "<b>Ввод стоимости до торга</b>\n\nНапример: <code>20000</code>"
|
||||
if s.CurrentChannel != "" {
|
||||
@@ -454,25 +549,25 @@ func (s *PurchaseOptionalDetails) renderInputPrompt(b *bot.Bot, mode bot.RenderM
|
||||
text += fmt.Sprintf("Канал: <b>@%s</b>\n\n", s.CurrentChannel)
|
||||
}
|
||||
text += "Выберите формат\n\n"
|
||||
keyboard := bot.Keyboard(
|
||||
bot.Row(
|
||||
bot.Button("1 / 24", "format:1 / 24"),
|
||||
bot.Button("1/48", "format:1/48"),
|
||||
bot.Button("1 / 72", "format:1 / 72"),
|
||||
keyboard := Keyboard(
|
||||
Row(
|
||||
Button("1 / 24", "format:1 / 24"),
|
||||
Button("1/48", "format:1/48"),
|
||||
Button("1 / 72", "format:1 / 72"),
|
||||
),
|
||||
bot.Row(
|
||||
bot.Button("1 / (7 дней)", "format:1 / (7 дней)"),
|
||||
bot.Button("1 / (30 дней)", "format:1 / (30 дней)"),
|
||||
bot.Button("1 / (без удаления)", "format:1 / (без удаления)"),
|
||||
Row(
|
||||
Button("1 / (7 дней)", "format:1 / (7 дней)"),
|
||||
Button("1 / (30 дней)", "format:1 / (30 дней)"),
|
||||
Button("1 / (без удаления)", "format:1 / (без удаления)"),
|
||||
),
|
||||
bot.Row(
|
||||
bot.Button("2 / 24", "format:2 / 24"),
|
||||
bot.Button("2/48", "format:2/48"),
|
||||
bot.Button("2/72", "format:2/72"),
|
||||
Row(
|
||||
Button("2 / 24", "format:2 / 24"),
|
||||
Button("2/48", "format:2/48"),
|
||||
Button("2/72", "format:2/72"),
|
||||
),
|
||||
bot.Row(
|
||||
bot.Button("Назад", s.backAction()),
|
||||
bot.Button("Свой формат", "format_custom"),
|
||||
Row(
|
||||
Button("Назад", s.backAction()),
|
||||
Button("Свой формат", "format_custom"),
|
||||
),
|
||||
)
|
||||
if mode == bot.EditMessage {
|
||||
@@ -491,8 +586,8 @@ func (s *PurchaseOptionalDetails) renderInputPrompt(b *bot.Bot, mode bot.RenderM
|
||||
return
|
||||
}
|
||||
|
||||
keyboard := bot.Keyboard(
|
||||
bot.Row(bot.Button("← Назад", s.backAction())),
|
||||
keyboard := Keyboard(
|
||||
Row(Button("← Назад", s.backAction())),
|
||||
)
|
||||
|
||||
if mode == bot.EditMessage {
|
||||
@@ -502,29 +597,70 @@ func (s *PurchaseOptionalDetails) renderInputPrompt(b *bot.Bot, mode bot.RenderM
|
||||
}
|
||||
}
|
||||
|
||||
func (s *PurchaseOptionalDetails) renderModeSelect(b *bot.Bot, mode bot.RenderMode) {
|
||||
text := "<i>… › Создание › Дополнительно › Выбор режима</i>\n\n"
|
||||
text += "<b>⚙ Переключить режим параметра</b>\n\n"
|
||||
|
||||
// Показываем текущую информацию о параметрах
|
||||
text += s.formatOptionalSummary()
|
||||
text += "\n\n"
|
||||
|
||||
text += "Выберите параметр для изменения режима:\n\n"
|
||||
|
||||
var rows [][]echotron.InlineKeyboardButton
|
||||
|
||||
// Дата размещения
|
||||
placementLabel := "Дата размещения: "
|
||||
if s.PlacementMode == "per_channel" {
|
||||
placementLabel += "для каждого"
|
||||
} else {
|
||||
placementLabel += "общий"
|
||||
}
|
||||
rows = append(rows, Row(Button(placementLabel, "toggle_param_mode:placement")))
|
||||
|
||||
// Стоимость
|
||||
costLabel := "Стоимость: "
|
||||
if s.CostMode == "per_channel" {
|
||||
costLabel += "для каждого"
|
||||
} else {
|
||||
costLabel += "общий"
|
||||
}
|
||||
rows = append(rows, Row(Button(costLabel, "toggle_param_mode:cost")))
|
||||
|
||||
// До торга
|
||||
costBeforeLabel := "До торга: "
|
||||
if s.CostBeforeMode == "per_channel" {
|
||||
costBeforeLabel += "для каждого"
|
||||
} else {
|
||||
costBeforeLabel += "общий"
|
||||
}
|
||||
rows = append(rows, Row(Button(costBeforeLabel, "toggle_param_mode:cost_before")))
|
||||
|
||||
// Формат
|
||||
formatLabel := "Формат: "
|
||||
if s.FormatMode == "per_channel" {
|
||||
formatLabel += "для каждого"
|
||||
} else {
|
||||
formatLabel += "общий"
|
||||
}
|
||||
rows = append(rows, Row(Button(formatLabel, "toggle_param_mode:format")))
|
||||
|
||||
rows = append(rows, Row(Button("← Назад", "back_to_optional")))
|
||||
|
||||
b.Render(text, Keyboard(rows...), mode)
|
||||
}
|
||||
|
||||
func (s *PurchaseOptionalDetails) renderParamScreens(b *bot.Bot, mode bot.RenderMode) bool {
|
||||
switch s.InputMode {
|
||||
case "placement_scope":
|
||||
s.renderParamScope(b, mode, "placement")
|
||||
return true
|
||||
case "placement_channels":
|
||||
s.renderParamChannels(b, mode, "placement")
|
||||
return true
|
||||
case "cost_scope":
|
||||
s.renderParamScope(b, mode, "cost")
|
||||
return true
|
||||
case "cost_channels":
|
||||
s.renderParamChannels(b, mode, "cost")
|
||||
return true
|
||||
case "cost_before_scope":
|
||||
s.renderParamScope(b, mode, "cost_before")
|
||||
return true
|
||||
case "cost_before_channels":
|
||||
s.renderParamChannels(b, mode, "cost_before")
|
||||
return true
|
||||
case "format_scope":
|
||||
s.renderParamScope(b, mode, "format")
|
||||
return true
|
||||
case "format_channels":
|
||||
s.renderParamChannels(b, mode, "format")
|
||||
return true
|
||||
@@ -533,31 +669,6 @@ func (s *PurchaseOptionalDetails) renderParamScreens(b *bot.Bot, mode bot.Render
|
||||
}
|
||||
}
|
||||
|
||||
func (s *PurchaseOptionalDetails) renderParamScope(b *bot.Bot, mode bot.RenderMode, param string) {
|
||||
title := s.paramTitle(param)
|
||||
text := "<i>… › Создание › Дополнительно</i>\n\n"
|
||||
text += fmt.Sprintf("<b>%s</b>\n\n", title)
|
||||
text += "Выберите режим\n\n"
|
||||
|
||||
commonLabel := "Общий"
|
||||
perLabel := "Для каждого"
|
||||
if s.paramMode(param) == "common" {
|
||||
commonLabel = "● " + commonLabel
|
||||
} else {
|
||||
perLabel = "● " + perLabel
|
||||
}
|
||||
|
||||
rows := [][]echotron.InlineKeyboardButton{
|
||||
bot.Row(
|
||||
bot.Button(commonLabel, fmt.Sprintf("param_mode_common:%s", param)),
|
||||
bot.Button(perLabel, fmt.Sprintf("param_mode_per:%s", param)),
|
||||
),
|
||||
bot.Row(bot.Button("← Назад", "back_to_optional")),
|
||||
}
|
||||
|
||||
b.Render(text, bot.Keyboard(rows...), mode)
|
||||
}
|
||||
|
||||
func (s *PurchaseOptionalDetails) renderParamChannels(b *bot.Bot, mode bot.RenderMode, param string) {
|
||||
s.CurrentParam = param
|
||||
text := "<i>… › Создание › Дополнительно</i>\n\n"
|
||||
@@ -568,7 +679,7 @@ func (s *PurchaseOptionalDetails) renderParamChannels(b *bot.Bot, mode bot.Rende
|
||||
total := len(s.Channels)
|
||||
if total == 0 {
|
||||
text += "\nКаналы не выбраны"
|
||||
b.Render(text, bot.Keyboard(bot.Row(bot.Button("← Назад", "back_to_optional"))), mode)
|
||||
b.Render(text, Keyboard(Row(Button("← Назад", "back_to_optional"))), mode)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -577,16 +688,74 @@ func (s *PurchaseOptionalDetails) renderParamChannels(b *bot.Bot, mode bot.Rende
|
||||
}
|
||||
start, end := ui.GetPageBounds(s.ParamPage, perPage, total)
|
||||
|
||||
// Устанавливаем режим по умолчанию
|
||||
if s.ChannelEditMode == "" {
|
||||
s.ChannelEditMode = "edit"
|
||||
}
|
||||
|
||||
var rows [][]echotron.InlineKeyboardButton
|
||||
|
||||
// Кнопки переключения режима
|
||||
editLabel := "Редактирование"
|
||||
copyLabel := "Копирование"
|
||||
if s.ChannelEditMode == "edit" {
|
||||
editLabel = "● " + editLabel
|
||||
} else {
|
||||
copyLabel = "● " + copyLabel
|
||||
}
|
||||
rows = append(rows, Row(
|
||||
Button(editLabel, "channel_mode:edit"),
|
||||
Button(copyLabel, "channel_mode:copy"),
|
||||
))
|
||||
|
||||
// Список каналов с кнопками в зависимости от режима
|
||||
for i := start; i < end; i++ {
|
||||
label := fmt.Sprintf("%d", i+1)
|
||||
rows = append(rows, bot.Row(
|
||||
bot.Button(label, fmt.Sprintf("param_edit:%s:%d", param, i)),
|
||||
bot.Button("⧉", fmt.Sprintf("param_copy:%s:%d", param, i)),
|
||||
bot.Button("⇲", fmt.Sprintf("param_paste:%s:%d", param, i)),
|
||||
bot.Button("Изменить", fmt.Sprintf("param_edit:%s:%d", param, i)),
|
||||
bot.Button("⌫", fmt.Sprintf("param_clear:%s:%d", param, i)),
|
||||
))
|
||||
channelUsername := s.Channels[i].Username
|
||||
|
||||
if s.ChannelEditMode == "copy" {
|
||||
// Режим копирования
|
||||
var channelRow []echotron.InlineKeyboardButton
|
||||
channelRow = append(channelRow, Button(label, fmt.Sprintf("param_edit:%s:%d", param, i)))
|
||||
channelRow = append(channelRow, Button("⧉", fmt.Sprintf("param_copy:%s:%d", param, i)))
|
||||
|
||||
// Кнопка вставки - только если есть данные в буфере
|
||||
if s.hasCopyBuffer(param) {
|
||||
channelRow = append(channelRow, Button("⇲", fmt.Sprintf("param_paste:%s:%d", param, i)))
|
||||
} else {
|
||||
channelRow = append(channelRow, Button(" ", "noop"))
|
||||
}
|
||||
|
||||
// Кнопка удаления - только если есть данные у канала
|
||||
if s.hasChannelValue(param, channelUsername) {
|
||||
channelRow = append(channelRow, Button("⌫", fmt.Sprintf("param_clear:%s:%d", param, i)))
|
||||
} else {
|
||||
channelRow = append(channelRow, Button(" ", "noop"))
|
||||
}
|
||||
|
||||
rows = append(rows, channelRow)
|
||||
} else {
|
||||
// Обычный режим редактирования
|
||||
var channelRow []echotron.InlineKeyboardButton
|
||||
channelRow = append(channelRow, Button(label, fmt.Sprintf("param_edit:%s:%d", param, i)))
|
||||
|
||||
// Кнопка "Добавить" или "Изменить" в зависимости от наличия данных
|
||||
hasValue := s.hasChannelValue(param, channelUsername)
|
||||
if hasValue {
|
||||
channelRow = append(channelRow, Button("Изменить", fmt.Sprintf("param_edit:%s:%d", param, i)))
|
||||
} else {
|
||||
channelRow = append(channelRow, Button("Добавить", fmt.Sprintf("param_edit:%s:%d", param, i)))
|
||||
}
|
||||
|
||||
// Кнопка удаления - только если есть данные у канала
|
||||
if hasValue {
|
||||
channelRow = append(channelRow, Button("⌫", fmt.Sprintf("param_clear:%s:%d", param, i)))
|
||||
} else {
|
||||
channelRow = append(channelRow, Button(" ", "noop"))
|
||||
}
|
||||
|
||||
rows = append(rows, channelRow)
|
||||
}
|
||||
}
|
||||
|
||||
pages := ui.CalculatePages(total, perPage)
|
||||
@@ -597,12 +766,11 @@ func (s *PurchaseOptionalDetails) renderParamChannels(b *bot.Bot, mode bot.Rende
|
||||
rows = append(rows, navRow)
|
||||
}
|
||||
|
||||
rows = append(rows, bot.Row(bot.Button("Применить ко всем", fmt.Sprintf("param_apply_all:%s", param))))
|
||||
rows = append(rows, bot.Row(
|
||||
bot.Button("← Назад", fmt.Sprintf("param_back:%s", param)),
|
||||
rows = append(rows, Row(
|
||||
Button("← Назад", fmt.Sprintf("param_back:%s", param)),
|
||||
))
|
||||
|
||||
b.Render(text, bot.Keyboard(rows...), mode)
|
||||
b.Render(text, Keyboard(rows...), mode)
|
||||
}
|
||||
|
||||
func (s *PurchaseOptionalDetails) renderParamChannelSummary(param string) string {
|
||||
@@ -616,20 +784,6 @@ func (s *PurchaseOptionalDetails) renderParamChannelSummary(param string) string
|
||||
|
||||
func (s *PurchaseOptionalDetails) handleParamCallback(b *bot.Bot, data string) bool {
|
||||
switch {
|
||||
case strings.HasPrefix(data, "param_mode_common:"):
|
||||
param := strings.TrimPrefix(data, "param_mode_common:")
|
||||
s.setParamMode(param, "common")
|
||||
s.CurrentChannel = ""
|
||||
s.CurrentParam = param
|
||||
s.openCommonEditor(b, param)
|
||||
return true
|
||||
case strings.HasPrefix(data, "param_mode_per:"):
|
||||
param := strings.TrimPrefix(data, "param_mode_per:")
|
||||
s.setParamMode(param, "per_channel")
|
||||
s.CurrentParam = param
|
||||
s.InputMode = param + "_channels"
|
||||
s.Enter(b, bot.EditMessage)
|
||||
return true
|
||||
case strings.HasPrefix(data, "param_edit:"):
|
||||
param, index, ok := s.parseParamIndex(data, "param_edit:")
|
||||
if !ok {
|
||||
@@ -686,11 +840,6 @@ func (s *PurchaseOptionalDetails) handleParamCallback(b *bot.Bot, data string) b
|
||||
}
|
||||
s.Enter(b, bot.EditMessage)
|
||||
return true
|
||||
case strings.HasPrefix(data, "param_apply_all:"):
|
||||
param := strings.TrimPrefix(data, "param_apply_all:")
|
||||
s.applyParamToAll(param)
|
||||
s.Enter(b, bot.EditMessage)
|
||||
return true
|
||||
case data == "prev" && strings.HasSuffix(s.InputMode, "_channels"):
|
||||
if s.ParamPage > 0 {
|
||||
s.ParamPage--
|
||||
@@ -702,8 +851,8 @@ func (s *PurchaseOptionalDetails) handleParamCallback(b *bot.Bot, data string) b
|
||||
s.Enter(b, bot.EditMessage)
|
||||
return true
|
||||
case strings.HasPrefix(data, "param_back:"):
|
||||
param := strings.TrimPrefix(data, "param_back:")
|
||||
s.InputMode = param + "_scope"
|
||||
// Возвращаемся на главный экран
|
||||
s.InputMode = ""
|
||||
s.Enter(b, bot.EditMessage)
|
||||
return true
|
||||
}
|
||||
@@ -734,7 +883,7 @@ func (s *PurchaseOptionalDetails) openCommonEditor(b *bot.Bot, param string) {
|
||||
BackState: s,
|
||||
}), bot.EditMessage)
|
||||
case "cost":
|
||||
s.InputMode = "cost_select"
|
||||
s.InputMode = "cost_value"
|
||||
s.ReturnMode = ""
|
||||
s.CurrentChannel = ""
|
||||
s.Enter(b, bot.EditMessage)
|
||||
@@ -766,7 +915,7 @@ func (s *PurchaseOptionalDetails) openChannelEditor(b *bot.Bot, param, username
|
||||
BackState: s,
|
||||
}), bot.EditMessage)
|
||||
case "cost":
|
||||
s.InputMode = "cost_select"
|
||||
s.InputMode = "cost_value"
|
||||
s.ReturnMode = "cost_channels"
|
||||
s.CurrentChannel = username
|
||||
s.Enter(b, bot.EditMessage)
|
||||
@@ -1084,6 +1233,40 @@ func (s *PurchaseOptionalDetails) formatCostBeforeForChannel(username string) st
|
||||
return fmt.Sprintf("%.0f₽", *value)
|
||||
}
|
||||
|
||||
func (s *PurchaseOptionalDetails) hasCopyBuffer(param string) bool {
|
||||
switch param {
|
||||
case "placement":
|
||||
return s.PlacementCopy != nil
|
||||
case "cost":
|
||||
return s.CostCopy != nil
|
||||
case "cost_before":
|
||||
return s.CostBeforeCopy != nil
|
||||
case "format":
|
||||
return s.FormatCopy != nil
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (s *PurchaseOptionalDetails) hasChannelValue(param, username string) bool {
|
||||
switch param {
|
||||
case "placement":
|
||||
value, ok := s.PlacementByChannel[username]
|
||||
return ok && value != nil
|
||||
case "cost":
|
||||
entry, ok := s.CostByChannel[username]
|
||||
return ok && entry.Value != nil
|
||||
case "cost_before":
|
||||
value, ok := s.CostBeforeByChannel[username]
|
||||
return ok && value != nil
|
||||
case "format":
|
||||
value, ok := s.FormatByChannel[username]
|
||||
return ok && value != ""
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (s *PurchaseOptionalDetails) hasPlacementValue() bool {
|
||||
if s.PlacementMode == "per_channel" && len(s.Channels) > 1 {
|
||||
for _, value := range s.PlacementByChannel {
|
||||
@@ -1209,16 +1392,16 @@ func (s *PurchaseOptionalDetails) backAction() string {
|
||||
|
||||
func (s *PurchaseOptionalDetails) createPurchase(b *bot.Bot, jwt string) {
|
||||
if len(s.Channels) == 0 {
|
||||
b.SendNew("❌ Добавьте хотя бы один канал", bot.Keyboard(
|
||||
bot.Row(bot.Button("← Назад", "back")),
|
||||
b.SendNew("❌ Добавьте хотя бы один канал", Keyboard(
|
||||
Row(Button("← Назад", "back")),
|
||||
))
|
||||
return
|
||||
}
|
||||
|
||||
purchaseType, ok := s.normalizePurchaseType()
|
||||
if !ok {
|
||||
b.SendNew("❌ Тип закупа: используйте «самопиар» или «стандарт»", bot.Keyboard(
|
||||
bot.Row(bot.Button("← Назад", "back")),
|
||||
b.SendNew("❌ Тип закупа: используйте «самопиар» или «стандарт»", Keyboard(
|
||||
Row(Button("← Назад", "back")),
|
||||
))
|
||||
return
|
||||
}
|
||||
@@ -1256,8 +1439,8 @@ func (s *PurchaseOptionalDetails) createPurchase(b *bot.Bot, jwt string) {
|
||||
)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to create purchase")
|
||||
b.SendNew("❌ Не удалось создать закуп", bot.Keyboard(
|
||||
bot.Row(bot.Button("← Назад", "back")),
|
||||
b.SendNew("❌ Не удалось создать закуп", Keyboard(
|
||||
Row(Button("← Назад", "back")),
|
||||
))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ func (s *Purchases) Enter(b *bot.Bot, mode bot.RenderMode) {
|
||||
jwt := b.Session.JWT
|
||||
if jwt == "" {
|
||||
log.Error().Msg("JWT is empty in session")
|
||||
b.SendNew("❌ Ошибка авторизации. Попробуйте /start", bot.Keyboard())
|
||||
b.SendNew("❌ Ошибка авторизации. Попробуйте /start", Keyboard())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ func (s *Purchases) renderPurchases(b *bot.Bot, mode bot.RenderMode, jwt string)
|
||||
)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to get purchases")
|
||||
b.SendNew("❌ Не удалось загрузить закупы", bot.Keyboard())
|
||||
b.SendNew("❌ Не удалось загрузить закупы", Keyboard())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -89,23 +89,23 @@ func (s *Purchases) renderPurchases(b *bot.Bot, mode bot.RenderMode, jwt string)
|
||||
totalChannels,
|
||||
)
|
||||
|
||||
buttons = append(buttons, bot.Row(
|
||||
bot.Button(buttonText, fmt.Sprintf("purchase:%s", purchase.ID)),
|
||||
buttons = append(buttons, Row(
|
||||
Button(buttonText, fmt.Sprintf("purchase:%s", purchase.ID)),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
// Кнопка создания нового закупа
|
||||
buttons = append(buttons, bot.Row(
|
||||
bot.Button("+ Создать закуп", "add_purchase"),
|
||||
buttons = append(buttons, Row(
|
||||
Button("+ Создать закуп", "add_purchase"),
|
||||
))
|
||||
|
||||
// Кнопка назад
|
||||
buttons = append(buttons, bot.Row(
|
||||
bot.Button("← Назад", "back"),
|
||||
buttons = append(buttons, Row(
|
||||
Button("← Назад", "back"),
|
||||
))
|
||||
|
||||
keyboard := bot.Keyboard(buttons...)
|
||||
keyboard := Keyboard(buttons...)
|
||||
b.Render(text, keyboard, mode)
|
||||
}
|
||||
|
||||
|
||||
@@ -76,12 +76,12 @@ func (s *SelectChannelsForPurchase) renderChannelSelection(b *bot.Bot, mode bot.
|
||||
if ch.PlannedCost != nil {
|
||||
buttonText = fmt.Sprintf("@%s — %.0f₽", ch.Username, *ch.PlannedCost)
|
||||
}
|
||||
slots = append(slots, bot.Button(buttonText, fmt.Sprintf("remove_channel:%d", i)))
|
||||
slots = append(slots, Button(buttonText, fmt.Sprintf("remove_channel:%d", i)))
|
||||
}
|
||||
|
||||
if len(s.Channels) > channelsPerPage {
|
||||
for len(slots) < channelsPerPage {
|
||||
slots = append(slots, bot.Button(" ", "empty"))
|
||||
slots = append(slots, Button(" ", "empty"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ func (s *SelectChannelsForPurchase) renderChannelSelection(b *bot.Bot, mode bot.
|
||||
if i+1 < len(slots) {
|
||||
row = append(row, slots[i+1])
|
||||
} else {
|
||||
row = append(row, bot.Button(" ", "empty"))
|
||||
row = append(row, Button(" ", "empty"))
|
||||
}
|
||||
buttons = append(buttons, row)
|
||||
}
|
||||
@@ -116,17 +116,17 @@ 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_step"),
|
||||
buttons = append(buttons, Row(
|
||||
Button("← Назад", "back"),
|
||||
Button("→ Далее", "next_step"),
|
||||
))
|
||||
} else {
|
||||
buttons = append(buttons, bot.Row(
|
||||
bot.Button("← Назад", "back"),
|
||||
buttons = append(buttons, Row(
|
||||
Button("← Назад", "back"),
|
||||
))
|
||||
}
|
||||
|
||||
keyboard := bot.Keyboard(buttons...)
|
||||
keyboard := Keyboard(buttons...)
|
||||
b.Render(text, keyboard, mode)
|
||||
}
|
||||
|
||||
@@ -152,8 +152,8 @@ func (s *SelectChannelsForPurchase) HandleCallback(b *bot.Bot, u *echotron.Updat
|
||||
|
||||
case "next_step":
|
||||
if len(s.Channels) == 0 {
|
||||
b.Edit("❌ Добавьте хотя бы один канал", bot.Keyboard(
|
||||
bot.Row(bot.Button("← Назад", "back_to_select")),
|
||||
b.Edit("❌ Добавьте хотя бы один канал", Keyboard(
|
||||
Row(Button("← Назад", "back_to_select")),
|
||||
))
|
||||
return
|
||||
}
|
||||
@@ -239,8 +239,8 @@ func (s *SelectChannelsForPurchase) createPurchase(b *bot.Bot, jwt string) {
|
||||
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to create purchase")
|
||||
b.Edit("❌ Не удалось создать закуп\n\nВозможные причины:\n• Один из каналов не найден\n• Ошибка сервера", bot.Keyboard(
|
||||
bot.Row(bot.Button("← Назад", "back_to_select")),
|
||||
b.Edit("❌ Не удалось создать закуп\n\nВозможные причины:\n• Один из каналов не найден\n• Ошибка сервера", Keyboard(
|
||||
Row(Button("← Назад", "back_to_select")),
|
||||
))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -20,20 +20,20 @@ func (s *SelectWorkspace) Enter(b *bot.Bot, mode bot.RenderMode) {
|
||||
jwt := b.Session.JWT
|
||||
if jwt == "" {
|
||||
log.Error().Msg("JWT is empty in session")
|
||||
b.SendNew("❌ Ошибка авторизации. Попробуйте /start", bot.Keyboard())
|
||||
b.SendNew("❌ Ошибка авторизации. Попробуйте /start", Keyboard())
|
||||
return
|
||||
}
|
||||
|
||||
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())
|
||||
b.SendNew("❌ Не удалось загрузить список workspace'ов", Keyboard())
|
||||
return
|
||||
}
|
||||
|
||||
if len(workspaces) == 0 {
|
||||
b.SendNew("У вас пока нет workspace'ов", bot.Keyboard(
|
||||
bot.Row(bot.Button("🏠 Главное меню", "back")),
|
||||
b.SendNew("У вас пока нет workspace'ов", Keyboard(
|
||||
Row(Button("🏠 Главное меню", "back")),
|
||||
))
|
||||
return
|
||||
}
|
||||
@@ -45,15 +45,15 @@ func (s *SelectWorkspace) Enter(b *bot.Bot, mode bot.RenderMode) {
|
||||
|
||||
// Создаем кнопки для каждого workspace
|
||||
for _, ws := range workspaces {
|
||||
buttons = append(buttons, bot.Row(
|
||||
bot.Button(ws.Name, fmt.Sprintf("select_workspace:%s", ws.ID)),
|
||||
buttons = append(buttons, Row(
|
||||
Button(ws.Name, fmt.Sprintf("select_workspace:%s", ws.ID)),
|
||||
))
|
||||
}
|
||||
|
||||
// Кнопка отмены
|
||||
buttons = append(buttons, bot.Row(bot.Button("❌ Отмена", "cancel")))
|
||||
buttons = append(buttons, Row(Button("❌ Отмена", "cancel")))
|
||||
|
||||
keyboard := bot.Keyboard(buttons...)
|
||||
keyboard := Keyboard(buttons...)
|
||||
b.Render(text, keyboard, mode)
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ func (s *SelectWorkspace) HandleCallback(b *bot.Bot, u *echotron.Update) {
|
||||
workspaceID := parts[1]
|
||||
|
||||
// Показываем индикатор загрузки
|
||||
b.Edit("⏳ Добавляем канал в workspace...", bot.Keyboard())
|
||||
b.Edit("⏳ Добавляем канал в workspace...", Keyboard())
|
||||
|
||||
// Вызываем API для привязки канала к workspace
|
||||
project, err := b.Backend.AttachChannelToWorkspace(
|
||||
@@ -92,8 +92,8 @@ func (s *SelectWorkspace) HandleCallback(b *bot.Bot, u *echotron.Update) {
|
||||
)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Str("channel_id", s.ChannelID).Str("workspace_id", workspaceID).Msg("Failed to attach channel to workspace")
|
||||
b.Edit("❌ Не удалось добавить канал в workspace", bot.Keyboard(
|
||||
bot.Row(bot.Button("← Назад", "back_to_select")),
|
||||
b.Edit("❌ Не удалось добавить канал в workspace", Keyboard(
|
||||
Row(Button("← Назад", "back_to_select")),
|
||||
))
|
||||
return
|
||||
}
|
||||
@@ -108,9 +108,9 @@ func (s *SelectWorkspace) HandleCallback(b *bot.Bot, u *echotron.Update) {
|
||||
project.Status,
|
||||
)
|
||||
|
||||
b.Edit(successText, bot.Keyboard(
|
||||
bot.Row(bot.Button("📋 Мои проекты", "my_projects")),
|
||||
bot.Row(bot.Button("🏠 Главное меню", "main_menu")),
|
||||
b.Edit(successText, Keyboard(
|
||||
Row(Button("📋 Мои проекты", "my_projects")),
|
||||
Row(Button("🏠 Главное меню", "main_menu")),
|
||||
))
|
||||
|
||||
case data == "back_to_select":
|
||||
|
||||
Reference in New Issue
Block a user