refactor
This commit is contained in:
@@ -24,8 +24,6 @@ type AddCreative struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const msgWaitCreative = `
|
const msgWaitCreative = `
|
||||||
<i>… › Креативы › Добавление</i>
|
|
||||||
|
|
||||||
<b>+ Добавить креатив</b>
|
<b>+ Добавить креатив</b>
|
||||||
|
|
||||||
📄 <b>Пришлите креатив который хотите добавить</b>
|
📄 <b>Пришлите креатив который хотите добавить</b>
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ type AddProject struct {
|
|||||||
func (s *AddProject) Enter(b *bot.Bot, mode bot.RenderMode) {
|
func (s *AddProject) Enter(b *bot.Bot, mode bot.RenderMode) {
|
||||||
buttons := [][]echotron.InlineKeyboardButton{
|
buttons := [][]echotron.InlineKeyboardButton{
|
||||||
Row(Button("← Назад", "back")),
|
Row(Button("← Назад", "back")),
|
||||||
|
Row(URLButton("Добавить", "tg://resolve?domain=chat_cruiser_bot&startchannel&admin=invite_users+post_messages+edit_messages+delete_messages")),
|
||||||
}
|
}
|
||||||
|
|
||||||
keyboard := Keyboard(buttons...)
|
keyboard := Keyboard(buttons...)
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ func (s *AddPurchase) Enter(b *bot.Bot, mode bot.RenderMode) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *AddPurchase) renderSelection(b *bot.Bot, mode bot.RenderMode, jwt string) {
|
func (s *AddPurchase) renderSelection(b *bot.Bot, mode bot.RenderMode, jwt string) {
|
||||||
text := "<i>… › Закупы › Создание</i>\n\n<b>Создание закупа</b>\n\n"
|
text := "<b>Создание закупа</b>\n\n"
|
||||||
text += "Выберите проект и креатив.\n\n"
|
text += "Выберите проект и креатив.\n\n"
|
||||||
|
|
||||||
var buttons [][]echotron.InlineKeyboardButton
|
var buttons [][]echotron.InlineKeyboardButton
|
||||||
|
|||||||
@@ -24,15 +24,7 @@ type CreativeDetails struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *CreativeDetails) Enter(b *bot.Bot, mode bot.RenderMode) {
|
func (s *CreativeDetails) Enter(b *bot.Bot, mode bot.RenderMode) {
|
||||||
jwt := b.Session.JWT
|
creative, err := b.Backend.GetCreative(context.Background(), b.Session.JWT, s.WorkspaceID, s.CreativeID)
|
||||||
if jwt == "" {
|
|
||||||
log.Error().Msg("JWT is empty in session")
|
|
||||||
b.SendNew("<b>❌ Ошибка авторизации</b>\n\nПопробуйте /start", Keyboard())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Загружаем креатив
|
|
||||||
creative, err := b.Backend.GetCreative(context.Background(), jwt, s.WorkspaceID, s.CreativeID)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Str("creative_id", s.CreativeID).Msg("Failed to get creative")
|
log.Error().Err(err).Str("creative_id", s.CreativeID).Msg("Failed to get creative")
|
||||||
b.SendNew("<b>❌ Не удалось загрузить креатив</b>", Keyboard(
|
b.SendNew("<b>❌ Не удалось загрузить креатив</b>", Keyboard(
|
||||||
@@ -476,25 +468,9 @@ func (s *CreativeDetails) updateCreative(b *bot.Bot) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *CreativeDetails) deleteCreative(b *bot.Bot) {
|
func (s *CreativeDetails) deleteCreative(b *bot.Bot) {
|
||||||
// JWT уже создан в Bot.Update(), просто берем из сессии
|
err := b.Backend.DeleteCreative(context.Background(), b.Session.JWT, s.WorkspaceID, s.CreativeID)
|
||||||
jwt := b.Session.JWT
|
|
||||||
if jwt == "" {
|
|
||||||
log.Error().Msg("JWT is empty in session")
|
|
||||||
b.SendNew("<b>❌ Ошибка авторизации</b>\n\nПопробуйте /start", Keyboard())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Отправляем запрос на удаление
|
|
||||||
err := b.Backend.DeleteCreative(
|
|
||||||
context.Background(),
|
|
||||||
jwt,
|
|
||||||
s.WorkspaceID,
|
|
||||||
s.CreativeID,
|
|
||||||
)
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Str("creative_id", s.CreativeID).Msg("Failed to delete creative")
|
|
||||||
// Показываем ошибку через панель управления
|
|
||||||
s.confirmingDelete = false // Сбрасываем флаг подтверждения
|
s.confirmingDelete = false // Сбрасываем флаг подтверждения
|
||||||
s.ShowControlPanel(b, "<b>❌ Не удалось удалить креатив</b>\n\nПроверьте подключение и попробуйте снова.", [][]echotron.InlineKeyboardButton{
|
s.ShowControlPanel(b, "<b>❌ Не удалось удалить креатив</b>\n\nПроверьте подключение и попробуйте снова.", [][]echotron.InlineKeyboardButton{
|
||||||
Row(Button("← Назад", "back")),
|
Row(Button("← Назад", "back")),
|
||||||
@@ -503,9 +479,6 @@ func (s *CreativeDetails) deleteCreative(b *bot.Bot) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info().Str("creative_id", s.CreativeID).Msg("Creative deleted successfully")
|
|
||||||
|
|
||||||
// Возвращаемся назад
|
|
||||||
if s.BackState != nil {
|
if s.BackState != nil {
|
||||||
b.SendNew("✅ Креатив успешно удален!", Keyboard())
|
b.SendNew("✅ Креатив успешно удален!", Keyboard())
|
||||||
b.SetState(s.BackState, bot.EditMessage)
|
b.SetState(s.BackState, bot.EditMessage)
|
||||||
|
|||||||
@@ -14,11 +14,10 @@ import (
|
|||||||
const creativesPerPage = 6
|
const creativesPerPage = 6
|
||||||
|
|
||||||
type Creatives struct {
|
type Creatives struct {
|
||||||
CurrentPage int
|
CurrentPage int
|
||||||
ProjectID string
|
ProjectID string
|
||||||
ProjectTitle string
|
WorkspaceID string
|
||||||
WorkspaceID string
|
BackState bot.State
|
||||||
BackState bot.State
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Creatives) Enter(b *bot.Bot, mode bot.RenderMode) {
|
func (s *Creatives) Enter(b *bot.Bot, mode bot.RenderMode) {
|
||||||
@@ -52,16 +51,8 @@ func (s *Creatives) renderCreatives(b *bot.Bot, mode bot.RenderMode, jwt string)
|
|||||||
var text string
|
var text string
|
||||||
var buttons [][]echotron.InlineKeyboardButton
|
var buttons [][]echotron.InlineKeyboardButton
|
||||||
|
|
||||||
// Навигация: 2 последних уровня
|
|
||||||
navPath := "… › Креативы"
|
|
||||||
if s.ProjectTitle != "" {
|
|
||||||
navPath = fmt.Sprintf("… › %s › Креативы", s.ProjectTitle)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(page.Items) == 0 {
|
if len(page.Items) == 0 {
|
||||||
text = fmt.Sprintf(`<i>%s</i>
|
text = `<b>Креативы</b>
|
||||||
|
|
||||||
<b>Креативы</b>
|
|
||||||
|
|
||||||
У вас пока нет креативов
|
У вас пока нет креативов
|
||||||
|
|
||||||
@@ -71,16 +62,13 @@ func (s *Creatives) renderCreatives(b *bot.Bot, mode bot.RenderMode, jwt string)
|
|||||||
‣ Шаблон для автопостинга
|
‣ Шаблон для автопостинга
|
||||||
‣ Варианты для A/B тестирования
|
‣ Варианты для A/B тестирования
|
||||||
|
|
||||||
Начните с добавления первого креатива`, navPath)
|
Начните с добавления первого креатива`
|
||||||
} else {
|
} else {
|
||||||
text = fmt.Sprintf(`<i>%s</i>
|
text = fmt.Sprintf(`<b>Креативы</b>%s
|
||||||
|
|
||||||
<b>Креативы</b>%s
|
|
||||||
|
|
||||||
Управление рекламными материалами
|
Управление рекламными материалами
|
||||||
|
|
||||||
Выберите креатив`,
|
Выберите креатив`,
|
||||||
navPath,
|
|
||||||
ui.FormatPageInfo(s.CurrentPage, page.Pages))
|
ui.FormatPageInfo(s.CurrentPage, page.Pages))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ type Login struct{}
|
|||||||
|
|
||||||
const msgHelloLogin = `
|
const msgHelloLogin = `
|
||||||
Привет, %s!
|
Привет, %s!
|
||||||
👇Нажми кнопку для входа на сайт.
|
Нажми кнопку для входа на сайт. 👇
|
||||||
`
|
`
|
||||||
|
|
||||||
func (s *Login) Enter(b *bot.Bot, mode bot.RenderMode) {
|
func (s *Login) Enter(b *bot.Bot, mode bot.RenderMode) {
|
||||||
|
|||||||
@@ -82,7 +82,6 @@ func (s *MyProjects) renderProjects(b *bot.Bot, mode bot.RenderMode, jwt string)
|
|||||||
|
|
||||||
var text string
|
var text string
|
||||||
var buttons [][]echotron.InlineKeyboardButton
|
var buttons [][]echotron.InlineKeyboardButton
|
||||||
navTitle := "Главное меню › Мои проекты"
|
|
||||||
headerTitle := "Мои проекты"
|
headerTitle := "Мои проекты"
|
||||||
emptyStateText := `У вас пока нет подключенных каналов.
|
emptyStateText := `У вас пока нет подключенных каналов.
|
||||||
|
|
||||||
@@ -96,24 +95,18 @@ func (s *MyProjects) renderProjects(b *bot.Bot, mode bot.RenderMode, jwt string)
|
|||||||
Начните с добавления первого проекта`
|
Начните с добавления первого проекта`
|
||||||
nonEmptyText := "Управление вашими Telegram-каналами.\n\nВыберите канал"
|
nonEmptyText := "Управление вашими Telegram-каналами.\n\nВыберите канал"
|
||||||
if s.OpenPurchases {
|
if s.OpenPurchases {
|
||||||
navTitle = "Главное меню › Закупы"
|
|
||||||
headerTitle = "Закупы"
|
headerTitle = "Закупы"
|
||||||
nonEmptyText = "Выберите проект для просмотра закупов"
|
nonEmptyText = "Выберите проект для просмотра закупов"
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(page.Items) == 0 {
|
if len(page.Items) == 0 {
|
||||||
text = fmt.Sprintf(`<i>%s</i>
|
text = fmt.Sprintf(`<b>%s</b>
|
||||||
|
|
||||||
<b>%s</b>
|
%s`, headerTitle, emptyStateText)
|
||||||
|
|
||||||
%s`, navTitle, headerTitle, emptyStateText)
|
|
||||||
} else {
|
} else {
|
||||||
text = fmt.Sprintf(`<i>%s</i>
|
text = fmt.Sprintf(`<b>%s</b>%s
|
||||||
|
|
||||||
<b>%s</b>%s
|
|
||||||
|
|
||||||
%s`,
|
%s`,
|
||||||
navTitle,
|
|
||||||
headerTitle,
|
headerTitle,
|
||||||
ui.FormatPageInfo(s.CurrentPage, page.Pages),
|
ui.FormatPageInfo(s.CurrentPage, page.Pages),
|
||||||
nonEmptyText)
|
nonEmptyText)
|
||||||
|
|||||||
@@ -34,9 +34,7 @@ func (s *ProjectDetails) Enter(b *bot.Bot, mode bot.RenderMode) {
|
|||||||
func (s *ProjectDetails) formatProjectDetails() string {
|
func (s *ProjectDetails) formatProjectDetails() string {
|
||||||
p := s.Project
|
p := s.Project
|
||||||
|
|
||||||
// Хлебные крошки и заголовок
|
text := fmt.Sprintf("<b>%s</b>\n\n", p.Title)
|
||||||
text := fmt.Sprintf("<i>… › Мои проекты › %s</i>\n\n", p.Title)
|
|
||||||
text += fmt.Sprintf("<b>%s</b>\n\n", p.Title)
|
|
||||||
|
|
||||||
// Информация о канале
|
// Информация о канале
|
||||||
if p.Username != nil && *p.Username != "" {
|
if p.Username != nil && *p.Username != "" {
|
||||||
@@ -132,10 +130,9 @@ func (s *ProjectDetails) HandleCallback(b *bot.Bot, u *echotron.Update) {
|
|||||||
case strings.HasPrefix(data, "creatives:"):
|
case strings.HasPrefix(data, "creatives:"):
|
||||||
s.confirmingLinkTypeChange = false // Сбрасываем флаг подтверждения
|
s.confirmingLinkTypeChange = false // Сбрасываем флаг подтверждения
|
||||||
b.SetState(&Creatives{
|
b.SetState(&Creatives{
|
||||||
ProjectID: s.Project.ID,
|
ProjectID: s.Project.ID,
|
||||||
ProjectTitle: s.Project.Title,
|
WorkspaceID: s.WorkspaceID,
|
||||||
WorkspaceID: s.WorkspaceID,
|
BackState: s,
|
||||||
BackState: s,
|
|
||||||
}, bot.EditMessage)
|
}, bot.EditMessage)
|
||||||
|
|
||||||
case strings.HasPrefix(data, "purchases:"):
|
case strings.HasPrefix(data, "purchases:"):
|
||||||
|
|||||||
@@ -12,11 +12,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type PurchaseDetails struct {
|
type PurchaseDetails struct {
|
||||||
WorkspaceID string
|
WorkspaceID string
|
||||||
ProjectID string
|
ProjectID string
|
||||||
ProjectTitle string
|
PurchaseID string
|
||||||
PurchaseID string
|
BackState bot.State
|
||||||
BackState bot.State
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *PurchaseDetails) Enter(b *bot.Bot, mode bot.RenderMode) {
|
func (s *PurchaseDetails) Enter(b *bot.Bot, mode bot.RenderMode) {
|
||||||
@@ -63,7 +62,7 @@ func (s *PurchaseDetails) renderDetails(b *bot.Bot, mode bot.RenderMode, jwt str
|
|||||||
totalPlacements += len(items)
|
totalPlacements += len(items)
|
||||||
}
|
}
|
||||||
|
|
||||||
text := "<i>… › Закупы › Детали</i>\n\n<b>Детали закупа</b>\n\n"
|
text := "<b>Детали закупа</b>\n\n"
|
||||||
|
|
||||||
// Информация о креативе
|
// Информация о креативе
|
||||||
if creative != nil {
|
if creative != nil {
|
||||||
|
|||||||
@@ -70,8 +70,7 @@ func (s *PurchaseOptionalDetails) Enter(b *bot.Bot, mode bot.RenderMode) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
text := "<i>… › Создание › Дополнительно</i>\n\n"
|
text := "<b>Страница создания закупа и необязательные составляющие</b>"
|
||||||
text += "<b>Страница создания закупа и необязательные составляющие</b>"
|
|
||||||
|
|
||||||
text += "\n\n"
|
text += "\n\n"
|
||||||
text += s.formatOptionalSummary()
|
text += s.formatOptionalSummary()
|
||||||
@@ -129,7 +128,7 @@ func (s *PurchaseOptionalDetails) HandleCallback(b *bot.Bot, u *echotron.Update)
|
|||||||
}
|
}
|
||||||
|
|
||||||
case "opt_payment_date":
|
case "opt_payment_date":
|
||||||
b.SetState(NewDateTimePicker(DateTimePickerConfig{
|
b.SetState(ui.NewDateTimePicker(ui.DateTimePickerConfig{
|
||||||
Title: "Дата оплаты",
|
Title: "Дата оплаты",
|
||||||
Key: "payment_date",
|
Key: "payment_date",
|
||||||
IncludeTime: true,
|
IncludeTime: true,
|
||||||
@@ -380,8 +379,8 @@ func (s *PurchaseOptionalDetails) formatDateTime(value *time.Time) string {
|
|||||||
if value == nil {
|
if value == nil {
|
||||||
return "—"
|
return "—"
|
||||||
}
|
}
|
||||||
local := value.In(mskLocation)
|
local := value.In(ui.MskLocation)
|
||||||
return fmt.Sprintf("%s %02d %s %s", weekdayName(local.Weekday()), local.Day(), monthShort(local.Month()), local.Format("15:04"))
|
return fmt.Sprintf("%s %02d %s %s", ui.WeekdayName(local.Weekday()), local.Day(), ui.MonthShort(local.Month()), local.Format("15:04"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *PurchaseOptionalDetails) formatText(value string) string {
|
func (s *PurchaseOptionalDetails) formatText(value string) string {
|
||||||
@@ -500,7 +499,7 @@ func (s *PurchaseOptionalDetails) costBeforeDeleteRow() []echotron.InlineKeyboar
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *PurchaseOptionalDetails) renderInputPrompt(b *bot.Bot, mode bot.RenderMode) {
|
func (s *PurchaseOptionalDetails) renderInputPrompt(b *bot.Bot, mode bot.RenderMode) {
|
||||||
text := "<i>… › Создание › Дополнительно</i>\n\n"
|
text := "<b>Дополнительно</b>\n\n"
|
||||||
switch s.InputMode {
|
switch s.InputMode {
|
||||||
case "type":
|
case "type":
|
||||||
text += "<b>Выберите тип закупа</b>"
|
text += "<b>Выберите тип закупа</b>"
|
||||||
@@ -598,8 +597,7 @@ func (s *PurchaseOptionalDetails) renderInputPrompt(b *bot.Bot, mode bot.RenderM
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *PurchaseOptionalDetails) renderModeSelect(b *bot.Bot, mode bot.RenderMode) {
|
func (s *PurchaseOptionalDetails) renderModeSelect(b *bot.Bot, mode bot.RenderMode) {
|
||||||
text := "<i>… › Создание › Дополнительно › Выбор режима</i>\n\n"
|
text := "<b>⚙ Переключить режим параметра</b>\n\n"
|
||||||
text += "<b>⚙ Переключить режим параметра</b>\n\n"
|
|
||||||
|
|
||||||
// Показываем текущую информацию о параметрах
|
// Показываем текущую информацию о параметрах
|
||||||
text += s.formatOptionalSummary()
|
text += s.formatOptionalSummary()
|
||||||
@@ -671,8 +669,7 @@ func (s *PurchaseOptionalDetails) renderParamScreens(b *bot.Bot, mode bot.Render
|
|||||||
|
|
||||||
func (s *PurchaseOptionalDetails) renderParamChannels(b *bot.Bot, mode bot.RenderMode, param string) {
|
func (s *PurchaseOptionalDetails) renderParamChannels(b *bot.Bot, mode bot.RenderMode, param string) {
|
||||||
s.CurrentParam = param
|
s.CurrentParam = param
|
||||||
text := "<i>… › Создание › Дополнительно</i>\n\n"
|
text := fmt.Sprintf("<b>%s — по каналам</b>\n\n", s.paramTitle(param))
|
||||||
text += fmt.Sprintf("<b>%s — по каналам</b>\n\n", s.paramTitle(param))
|
|
||||||
text += s.renderParamChannelSummary(param) + "\n\n"
|
text += s.renderParamChannelSummary(param) + "\n\n"
|
||||||
|
|
||||||
const perPage = 5
|
const perPage = 5
|
||||||
@@ -866,7 +863,10 @@ func (s *PurchaseOptionalDetails) parseParamIndex(data, prefix string) (string,
|
|||||||
return "", 0, false
|
return "", 0, false
|
||||||
}
|
}
|
||||||
param := parts[0]
|
param := parts[0]
|
||||||
index := parseInt(parts[1])
|
index, err := strconv.Atoi(parts[1])
|
||||||
|
if err != nil {
|
||||||
|
return "", 0, false
|
||||||
|
}
|
||||||
return param, index, true
|
return param, index, true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -875,7 +875,7 @@ func (s *PurchaseOptionalDetails) openCommonEditor(b *bot.Bot, param string) {
|
|||||||
case "placement":
|
case "placement":
|
||||||
s.InputMode = ""
|
s.InputMode = ""
|
||||||
s.ReturnMode = ""
|
s.ReturnMode = ""
|
||||||
b.SetState(NewDateTimePicker(DateTimePickerConfig{
|
b.SetState(ui.NewDateTimePicker(ui.DateTimePickerConfig{
|
||||||
Title: "Дата и время размещения",
|
Title: "Дата и время размещения",
|
||||||
Key: "placement_datetime",
|
Key: "placement_datetime",
|
||||||
IncludeTime: true,
|
IncludeTime: true,
|
||||||
@@ -907,7 +907,7 @@ func (s *PurchaseOptionalDetails) openChannelEditor(b *bot.Bot, param, username
|
|||||||
switch param {
|
switch param {
|
||||||
case "placement":
|
case "placement":
|
||||||
s.InputMode = "placement_channels"
|
s.InputMode = "placement_channels"
|
||||||
b.SetState(NewDateTimePicker(DateTimePickerConfig{
|
b.SetState(ui.NewDateTimePicker(ui.DateTimePickerConfig{
|
||||||
Title: "Дата и время размещения",
|
Title: "Дата и время размещения",
|
||||||
Key: "placement_datetime:" + username,
|
Key: "placement_datetime:" + username,
|
||||||
IncludeTime: true,
|
IncludeTime: true,
|
||||||
@@ -968,7 +968,7 @@ func (s *PurchaseOptionalDetails) pasteParamValue(param, username string) {
|
|||||||
s.PlacementByChannel[username] = nil
|
s.PlacementByChannel[username] = nil
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
value := s.PlacementCopy.In(mskLocation)
|
value := s.PlacementCopy.In(ui.MskLocation)
|
||||||
s.PlacementByChannel[username] = &value
|
s.PlacementByChannel[username] = &value
|
||||||
case "cost":
|
case "cost":
|
||||||
if s.CostCopy == nil {
|
if s.CostCopy == nil {
|
||||||
@@ -1467,11 +1467,10 @@ func (s *PurchaseOptionalDetails) createPurchase(b *bot.Bot, jwt string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
b.SetState(&PurchaseDetails{
|
b.SetState(&PurchaseDetails{
|
||||||
WorkspaceID: s.WorkspaceID,
|
WorkspaceID: s.WorkspaceID,
|
||||||
ProjectID: s.ProjectID,
|
ProjectID: s.ProjectID,
|
||||||
ProjectTitle: s.ProjectTitle,
|
PurchaseID: purchase.ID,
|
||||||
PurchaseID: purchase.ID,
|
BackState: s.BackState,
|
||||||
BackState: s.BackState,
|
|
||||||
}, bot.NewMessage)
|
}, bot.NewMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -40,12 +40,7 @@ func (s *Purchases) renderPurchases(b *bot.Bot, mode bot.RenderMode, jwt string)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Навигация: 2 последних уровня
|
text := "<b>Закупы</b>\n\n"
|
||||||
navPath := "… › Закупы"
|
|
||||||
if s.ProjectTitle != "" {
|
|
||||||
navPath = fmt.Sprintf("… › %s › Закупы", s.ProjectTitle)
|
|
||||||
}
|
|
||||||
text := fmt.Sprintf("<i>%s</i>\n\n<b>Закупы</b>\n\n", navPath)
|
|
||||||
|
|
||||||
var buttons [][]echotron.InlineKeyboardButton
|
var buttons [][]echotron.InlineKeyboardButton
|
||||||
|
|
||||||
@@ -136,11 +131,10 @@ func (s *Purchases) HandleCallback(b *bot.Bot, u *echotron.Update) {
|
|||||||
if len(data) > 9 && data[:9] == "purchase:" {
|
if len(data) > 9 && data[:9] == "purchase:" {
|
||||||
purchaseID := data[9:]
|
purchaseID := data[9:]
|
||||||
b.SetState(&PurchaseDetails{
|
b.SetState(&PurchaseDetails{
|
||||||
WorkspaceID: s.WorkspaceID,
|
WorkspaceID: s.WorkspaceID,
|
||||||
ProjectID: s.ProjectID,
|
ProjectID: s.ProjectID,
|
||||||
ProjectTitle: s.ProjectTitle,
|
PurchaseID: purchaseID,
|
||||||
PurchaseID: purchaseID,
|
BackState: s,
|
||||||
BackState: s,
|
|
||||||
}, bot.EditMessage)
|
}, bot.EditMessage)
|
||||||
} else {
|
} else {
|
||||||
s.Enter(b, bot.NewMessage)
|
s.Enter(b, bot.NewMessage)
|
||||||
|
|||||||
@@ -38,8 +38,7 @@ func (s *SelectChannelsForPurchase) Enter(b *bot.Bot, mode bot.RenderMode) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *SelectChannelsForPurchase) renderChannelSelection(b *bot.Bot, mode bot.RenderMode) {
|
func (s *SelectChannelsForPurchase) renderChannelSelection(b *bot.Bot, mode bot.RenderMode) {
|
||||||
text := "<i>… › Создание › Каналы</i>\n\n"
|
text := "<b>Создание закупа</b>\n\n"
|
||||||
text += "<b>Создание закупа</b>\n\n"
|
|
||||||
text += "<b>Шаг 2/2:</b> Добавление каналов\n\n"
|
text += "<b>Шаг 2/2:</b> Добавление каналов\n\n"
|
||||||
|
|
||||||
var buttons [][]echotron.InlineKeyboardButton
|
var buttons [][]echotron.InlineKeyboardButton
|
||||||
@@ -268,11 +267,10 @@ func (s *SelectChannelsForPurchase) createPurchase(b *bot.Bot, jwt string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
b.SetState(&PurchaseDetails{
|
b.SetState(&PurchaseDetails{
|
||||||
WorkspaceID: s.WorkspaceID,
|
WorkspaceID: s.WorkspaceID,
|
||||||
ProjectID: s.ProjectID,
|
ProjectID: s.ProjectID,
|
||||||
ProjectTitle: s.ProjectTitle,
|
PurchaseID: purchase.ID,
|
||||||
PurchaseID: purchase.ID,
|
BackState: s.BackState,
|
||||||
BackState: s.BackState,
|
|
||||||
}, bot.EditMessage)
|
}, bot.EditMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package screens
|
package ui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -18,7 +18,7 @@ const (
|
|||||||
dtpCallbackPrefix = "dtp:"
|
dtpCallbackPrefix = "dtp:"
|
||||||
)
|
)
|
||||||
|
|
||||||
var mskLocation = time.FixedZone("MSK", 3*60*60)
|
var MskLocation = time.FixedZone("MSK", 3*60*60)
|
||||||
|
|
||||||
type DateTimePickerConfig struct {
|
type DateTimePickerConfig struct {
|
||||||
Title string
|
Title string
|
||||||
@@ -47,13 +47,13 @@ type DateTimePicker struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewDateTimePicker(cfg DateTimePickerConfig) *DateTimePicker {
|
func NewDateTimePicker(cfg DateTimePickerConfig) *DateTimePicker {
|
||||||
now := time.Now().In(mskLocation)
|
now := time.Now().In(MskLocation)
|
||||||
year := now.Year()
|
year := now.Year()
|
||||||
month := now.Month()
|
month := now.Month()
|
||||||
var selected *time.Time
|
var selected *time.Time
|
||||||
|
|
||||||
if cfg.Selected != nil {
|
if cfg.Selected != nil {
|
||||||
value := cfg.Selected.In(mskLocation)
|
value := cfg.Selected.In(MskLocation)
|
||||||
selected = &value
|
selected = &value
|
||||||
year = value.Year()
|
year = value.Year()
|
||||||
month = value.Month()
|
month = value.Month()
|
||||||
@@ -72,9 +72,8 @@ func NewDateTimePicker(cfg DateTimePickerConfig) *DateTimePicker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *DateTimePicker) Enter(b *bot.Bot, mode bot.RenderMode) {
|
func (s *DateTimePicker) Enter(b *bot.Bot, mode bot.RenderMode) {
|
||||||
text := fmt.Sprintf("<i>… › %s</i>\n\n", s.Title)
|
text := fmt.Sprintf("<b>%s</b>\n\n", s.Title)
|
||||||
text += fmt.Sprintf("<b>%s</b>\n", s.formatSelectedDateTime())
|
text += fmt.Sprintf("%s\n\n", s.formatSelectedDateTime())
|
||||||
text += "\n"
|
|
||||||
switch s.view {
|
switch s.view {
|
||||||
case pickerViewCalendar:
|
case pickerViewCalendar:
|
||||||
text += s.renderCalendarTitle()
|
text += s.renderCalendarTitle()
|
||||||
@@ -166,7 +165,7 @@ func (s *DateTimePicker) handleBack(b *bot.Bot) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *DateTimePicker) renderCalendarTitle() string {
|
func (s *DateTimePicker) renderCalendarTitle() string {
|
||||||
if s.year == time.Now().In(mskLocation).Year() {
|
if s.year == time.Now().In(MskLocation).Year() {
|
||||||
return "\n"
|
return "\n"
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("%d\n\n", s.year)
|
return fmt.Sprintf("%d\n\n", s.year)
|
||||||
@@ -175,43 +174,46 @@ func (s *DateTimePicker) renderCalendarTitle() string {
|
|||||||
func (s *DateTimePicker) calendarKeyboard() echotron.InlineKeyboardMarkup {
|
func (s *DateTimePicker) calendarKeyboard() echotron.InlineKeyboardMarkup {
|
||||||
var rows [][]echotron.InlineKeyboardButton
|
var rows [][]echotron.InlineKeyboardButton
|
||||||
|
|
||||||
rows = append(rows, Row(
|
rows = append(rows, []echotron.InlineKeyboardButton{
|
||||||
s.monthPrevButton(),
|
s.monthPrevButton(),
|
||||||
Button(monthName(s.month), dtpCallbackPrefix+"open_months"),
|
{Text: monthName(s.month), CallbackData: dtpCallbackPrefix + "open_months"},
|
||||||
Button("→", dtpCallbackPrefix+"month_next"),
|
{Text: "→", CallbackData: dtpCallbackPrefix + "month_next"},
|
||||||
))
|
})
|
||||||
|
|
||||||
rows = append(rows, Row(
|
rows = append(rows, []echotron.InlineKeyboardButton{
|
||||||
Button("Пн", "empty"),
|
{Text: "Пн", CallbackData: "empty"},
|
||||||
Button("Вт", "empty"),
|
{Text: "Вт", CallbackData: "empty"},
|
||||||
Button("Ср", "empty"),
|
{Text: "Ср", CallbackData: "empty"},
|
||||||
Button("Чт", "empty"),
|
{Text: "Чт", CallbackData: "empty"},
|
||||||
Button("Пт", "empty"),
|
{Text: "Пт", CallbackData: "empty"},
|
||||||
Button("Сб", "empty"),
|
{Text: "Сб", CallbackData: "empty"},
|
||||||
Button("Вс", "empty"),
|
{Text: "Вс", CallbackData: "empty"},
|
||||||
))
|
})
|
||||||
|
|
||||||
firstOfMonth := time.Date(s.year, s.month, 1, 0, 0, 0, 0, mskLocation)
|
firstOfMonth := time.Date(s.year, s.month, 1, 0, 0, 0, 0, MskLocation)
|
||||||
weekday := int(firstOfMonth.Weekday())
|
weekday := int(firstOfMonth.Weekday())
|
||||||
if weekday == 0 {
|
if weekday == 0 {
|
||||||
weekday = 7
|
weekday = 7
|
||||||
}
|
}
|
||||||
daysInMonth := daysInMonth(s.year, s.month)
|
daysInMonth := daysInMonth(s.year, s.month)
|
||||||
today := time.Now().In(mskLocation)
|
today := time.Now().In(MskLocation)
|
||||||
todayDate := time.Date(today.Year(), today.Month(), today.Day(), 0, 0, 0, 0, mskLocation)
|
todayDate := time.Date(today.Year(), today.Month(), today.Day(), 0, 0, 0, 0, MskLocation)
|
||||||
|
|
||||||
var row []echotron.InlineKeyboardButton
|
var row []echotron.InlineKeyboardButton
|
||||||
for i := 1; i < weekday; i++ {
|
for i := 1; i < weekday; i++ {
|
||||||
row = append(row, Button(" ", "empty"))
|
row = append(row, echotron.InlineKeyboardButton{Text: " ", CallbackData: "empty"})
|
||||||
}
|
}
|
||||||
|
|
||||||
for day := 1; day <= daysInMonth; day++ {
|
for day := 1; day <= daysInMonth; day++ {
|
||||||
date := time.Date(s.year, s.month, day, 0, 0, 0, 0, mskLocation)
|
date := time.Date(s.year, s.month, day, 0, 0, 0, 0, MskLocation)
|
||||||
if date.Before(todayDate) {
|
if date.Before(todayDate) {
|
||||||
row = append(row, Button("-", "empty"))
|
row = append(row, echotron.InlineKeyboardButton{Text: "-", CallbackData: "empty"})
|
||||||
} else {
|
} else {
|
||||||
label := fmt.Sprintf("%d", day)
|
label := fmt.Sprintf("%d", day)
|
||||||
row = append(row, Button(label, fmt.Sprintf("%sday:%04d-%02d-%02d", dtpCallbackPrefix, s.year, int(s.month), day)))
|
row = append(row, echotron.InlineKeyboardButton{
|
||||||
|
Text: label,
|
||||||
|
CallbackData: fmt.Sprintf("%sday:%04d-%02d-%02d", dtpCallbackPrefix, s.year, int(s.month), day),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
if len(row) == 7 {
|
if len(row) == 7 {
|
||||||
rows = append(rows, row)
|
rows = append(rows, row)
|
||||||
@@ -221,17 +223,17 @@ func (s *DateTimePicker) calendarKeyboard() echotron.InlineKeyboardMarkup {
|
|||||||
|
|
||||||
if len(row) > 0 {
|
if len(row) > 0 {
|
||||||
for len(row) < 7 {
|
for len(row) < 7 {
|
||||||
row = append(row, Button(" ", "empty"))
|
row = append(row, echotron.InlineKeyboardButton{Text: " ", CallbackData: "empty"})
|
||||||
}
|
}
|
||||||
rows = append(rows, row)
|
rows = append(rows, row)
|
||||||
}
|
}
|
||||||
|
|
||||||
rows = append(rows, Row(
|
rows = append(rows, []echotron.InlineKeyboardButton{
|
||||||
Button("← Назад", "back"),
|
{Text: "← Назад", CallbackData: "back"},
|
||||||
Button("Отмена", "cancel"),
|
{Text: "Отмена", CallbackData: "cancel"},
|
||||||
))
|
})
|
||||||
|
|
||||||
return Keyboard(rows...)
|
return echotron.InlineKeyboardMarkup{InlineKeyboard: rows}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *DateTimePicker) monthsKeyboard() echotron.InlineKeyboardMarkup {
|
func (s *DateTimePicker) monthsKeyboard() echotron.InlineKeyboardMarkup {
|
||||||
@@ -239,59 +241,61 @@ func (s *DateTimePicker) monthsKeyboard() echotron.InlineKeyboardMarkup {
|
|||||||
|
|
||||||
months := []string{"Янв", "Фев", "Мар", "Апр", "Май", "Июн", "Июл", "Авг", "Сен", "Окт", "Ноя", "Дек"}
|
months := []string{"Янв", "Фев", "Мар", "Апр", "Май", "Июн", "Июл", "Авг", "Сен", "Окт", "Ноя", "Дек"}
|
||||||
for i := 0; i < 12; i += 3 {
|
for i := 0; i < 12; i += 3 {
|
||||||
rows = append(rows, Row(
|
rows = append(rows, []echotron.InlineKeyboardButton{
|
||||||
Button(months[i], fmt.Sprintf("%smonth:%d", dtpCallbackPrefix, i+1)),
|
{Text: months[i], CallbackData: fmt.Sprintf("%smonth:%d", dtpCallbackPrefix, i+1)},
|
||||||
Button(months[i+1], fmt.Sprintf("%smonth:%d", dtpCallbackPrefix, i+2)),
|
{Text: months[i+1], CallbackData: fmt.Sprintf("%smonth:%d", dtpCallbackPrefix, i+2)},
|
||||||
Button(months[i+2], fmt.Sprintf("%smonth:%d", dtpCallbackPrefix, i+3)),
|
{Text: months[i+2], CallbackData: fmt.Sprintf("%smonth:%d", dtpCallbackPrefix, i+3)},
|
||||||
))
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
rows = append(rows, Row(
|
rows = append(rows, []echotron.InlineKeyboardButton{
|
||||||
Button("← Назад", "back"),
|
{Text: "← Назад", CallbackData: "back"},
|
||||||
))
|
})
|
||||||
|
|
||||||
return Keyboard(rows...)
|
return echotron.InlineKeyboardMarkup{InlineKeyboard: rows}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *DateTimePicker) timeCombinedKeyboard() echotron.InlineKeyboardMarkup {
|
func (s *DateTimePicker) timeCombinedKeyboard() echotron.InlineKeyboardMarkup {
|
||||||
var rows [][]echotron.InlineKeyboardButton
|
var rows [][]echotron.InlineKeyboardButton
|
||||||
rows = append(rows, Row(Button("Часы", "empty")))
|
rows = append(rows, []echotron.InlineKeyboardButton{{Text: "Часы", CallbackData: "empty"}})
|
||||||
for i := 0; i < 24; i += 6 {
|
for i := 0; i < 24; i += 6 {
|
||||||
rows = append(rows, Row(
|
rows = append(rows, []echotron.InlineKeyboardButton{
|
||||||
s.hourButton(i),
|
s.hourButton(i),
|
||||||
s.hourButton(i+1),
|
s.hourButton(i + 1),
|
||||||
s.hourButton(i+2),
|
s.hourButton(i + 2),
|
||||||
s.hourButton(i+3),
|
s.hourButton(i + 3),
|
||||||
s.hourButton(i+4),
|
s.hourButton(i + 4),
|
||||||
s.hourButton(i+5),
|
s.hourButton(i + 5),
|
||||||
))
|
})
|
||||||
}
|
}
|
||||||
rows = append(rows, Row(Button("Минуты", "empty")))
|
rows = append(rows, []echotron.InlineKeyboardButton{{Text: "Минуты", CallbackData: "empty"}})
|
||||||
minutes := []int{0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55}
|
minutes := []int{0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55}
|
||||||
for i := 0; i < len(minutes); i += 6 {
|
for i := 0; i < len(minutes); i += 6 {
|
||||||
rows = append(rows, Row(
|
rows = append(rows, []echotron.InlineKeyboardButton{
|
||||||
s.minuteButton(minutes[i]),
|
s.minuteButton(minutes[i]),
|
||||||
s.minuteButton(minutes[i+1]),
|
s.minuteButton(minutes[i+1]),
|
||||||
s.minuteButton(minutes[i+2]),
|
s.minuteButton(minutes[i+2]),
|
||||||
s.minuteButton(minutes[i+3]),
|
s.minuteButton(minutes[i+3]),
|
||||||
s.minuteButton(minutes[i+4]),
|
s.minuteButton(minutes[i+4]),
|
||||||
s.minuteButton(minutes[i+5]),
|
s.minuteButton(minutes[i+5]),
|
||||||
))
|
})
|
||||||
}
|
}
|
||||||
rows = append(rows, Row(
|
rows = append(rows, []echotron.InlineKeyboardButton{
|
||||||
Button("← Назад", "back"),
|
{Text: "← Назад", CallbackData: "back"},
|
||||||
Button("Готово", dtpCallbackPrefix+"confirm"),
|
{Text: "Готово", CallbackData: dtpCallbackPrefix + "confirm"},
|
||||||
))
|
})
|
||||||
return Keyboard(rows...)
|
return echotron.InlineKeyboardMarkup{InlineKeyboard: rows}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *DateTimePicker) confirmKeyboard() echotron.InlineKeyboardMarkup {
|
func (s *DateTimePicker) confirmKeyboard() echotron.InlineKeyboardMarkup {
|
||||||
return Keyboard(
|
return echotron.InlineKeyboardMarkup{
|
||||||
Row(
|
InlineKeyboard: [][]echotron.InlineKeyboardButton{
|
||||||
Button("← Назад", "back"),
|
{
|
||||||
Button("Готово", dtpCallbackPrefix+"confirm"),
|
{Text: "← Назад", CallbackData: "back"},
|
||||||
),
|
{Text: "Готово", CallbackData: dtpCallbackPrefix + "confirm"},
|
||||||
)
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *DateTimePicker) renderSelectedDateTime() string {
|
func (s *DateTimePicker) renderSelectedDateTime() string {
|
||||||
@@ -306,7 +310,7 @@ func (s *DateTimePicker) renderSelectedDateTime() string {
|
|||||||
if s.selectedMinute != nil {
|
if s.selectedMinute != nil {
|
||||||
minute = *s.selectedMinute
|
minute = *s.selectedMinute
|
||||||
}
|
}
|
||||||
value := time.Date(s.selectedDate.Year(), s.selectedDate.Month(), s.selectedDate.Day(), hour, minute, 0, 0, mskLocation)
|
value := time.Date(s.selectedDate.Year(), s.selectedDate.Month(), s.selectedDate.Day(), hour, minute, 0, 0, MskLocation)
|
||||||
return fmt.Sprintf("<b>%s</b>", formatCompactDateTime(value))
|
return fmt.Sprintf("<b>%s</b>", formatCompactDateTime(value))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -335,15 +339,15 @@ func (s *DateTimePicker) formatSelectedDateTime() string {
|
|||||||
if s.selectedHour != nil && s.selectedMinute != nil {
|
if s.selectedHour != nil && s.selectedMinute != nil {
|
||||||
timeLabel = fmt.Sprintf("%02d:%02d", *s.selectedHour, *s.selectedMinute)
|
timeLabel = fmt.Sprintf("%02d:%02d", *s.selectedHour, *s.selectedMinute)
|
||||||
}
|
}
|
||||||
weekday := weekdayName(displayDate.Weekday())
|
weekday := WeekdayName(displayDate.Weekday())
|
||||||
month := monthShort(displayDate.Month())
|
month := MonthShort(displayDate.Month())
|
||||||
return fmt.Sprintf("%s %02d %s %s %dг.", weekday, displayDate.Day(), month, timeLabel, displayDate.Year())
|
return fmt.Sprintf("%s %02d %s %s %dг.", weekday, displayDate.Day(), month, timeLabel, displayDate.Year())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *DateTimePicker) displayDate() (time.Time, bool) {
|
func (s *DateTimePicker) displayDate() (time.Time, bool) {
|
||||||
if s.selectedDate == nil {
|
if s.selectedDate == nil {
|
||||||
if s.view == pickerViewCalendar || s.view == pickerViewMonths {
|
if s.view == pickerViewCalendar || s.view == pickerViewMonths {
|
||||||
return time.Date(s.year, s.month, 1, 0, 0, 0, 0, mskLocation), true
|
return time.Date(s.year, s.month, 1, 0, 0, 0, 0, MskLocation), true
|
||||||
}
|
}
|
||||||
return time.Time{}, false
|
return time.Time{}, false
|
||||||
}
|
}
|
||||||
@@ -355,15 +359,15 @@ func (s *DateTimePicker) displayDate() (time.Time, bool) {
|
|||||||
if day > maxDay {
|
if day > maxDay {
|
||||||
day = maxDay
|
day = maxDay
|
||||||
}
|
}
|
||||||
return time.Date(s.year, s.month, day, 0, 0, 0, 0, mskLocation), true
|
return time.Date(s.year, s.month, day, 0, 0, 0, 0, MskLocation), true
|
||||||
}
|
}
|
||||||
|
|
||||||
return s.selectedDate.In(mskLocation), true
|
return s.selectedDate.In(MskLocation), true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *DateTimePicker) handleDaySelect(payload string) {
|
func (s *DateTimePicker) handleDaySelect(payload string) {
|
||||||
datePart := strings.TrimPrefix(payload, "day:")
|
datePart := strings.TrimPrefix(payload, "day:")
|
||||||
parsed, err := time.ParseInLocation("2006-01-02", datePart, mskLocation)
|
parsed, err := time.ParseInLocation("2006-01-02", datePart, MskLocation)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -401,9 +405,12 @@ func (s *DateTimePicker) handleMinuteSelect(payload string) {
|
|||||||
|
|
||||||
func (s *DateTimePicker) confirmSelection(b *bot.Bot) {
|
func (s *DateTimePicker) confirmSelection(b *bot.Bot) {
|
||||||
if s.selectedDate == nil {
|
if s.selectedDate == nil {
|
||||||
|
s.Enter(b, bot.EditMessage)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if s.IncludeTime && (s.selectedHour == nil || s.selectedMinute == nil) {
|
if s.IncludeTime && (s.selectedHour == nil || s.selectedMinute == nil) {
|
||||||
|
s.view = pickerViewTimeCombined
|
||||||
|
s.Enter(b, bot.EditMessage)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
hour := 0
|
hour := 0
|
||||||
@@ -414,7 +421,7 @@ func (s *DateTimePicker) confirmSelection(b *bot.Bot) {
|
|||||||
if s.selectedMinute != nil {
|
if s.selectedMinute != nil {
|
||||||
minute = *s.selectedMinute
|
minute = *s.selectedMinute
|
||||||
}
|
}
|
||||||
value := time.Date(s.selectedDate.Year(), s.selectedDate.Month(), s.selectedDate.Day(), hour, minute, 0, 0, mskLocation)
|
value := time.Date(s.selectedDate.Year(), s.selectedDate.Month(), s.selectedDate.Day(), hour, minute, 0, 0, MskLocation)
|
||||||
if target, ok := s.BackState.(DateTimeSelectionTarget); ok {
|
if target, ok := s.BackState.(DateTimeSelectionTarget); ok {
|
||||||
target.SetDateTimeSelection(s.Key, value)
|
target.SetDateTimeSelection(s.Key, value)
|
||||||
}
|
}
|
||||||
@@ -426,32 +433,38 @@ func (s *DateTimePicker) confirmSelection(b *bot.Bot) {
|
|||||||
func (s *DateTimePicker) hourButton(hour int) echotron.InlineKeyboardButton {
|
func (s *DateTimePicker) hourButton(hour int) echotron.InlineKeyboardButton {
|
||||||
label := fmt.Sprintf("%02d", hour)
|
label := fmt.Sprintf("%02d", hour)
|
||||||
if s.isHourDisabled(hour) {
|
if s.isHourDisabled(hour) {
|
||||||
return Button("-", "empty")
|
return echotron.InlineKeyboardButton{Text: "-", CallbackData: "empty"}
|
||||||
}
|
}
|
||||||
if s.selectedHour != nil && *s.selectedHour == hour {
|
if s.selectedHour != nil && *s.selectedHour == hour {
|
||||||
label = "● " + label
|
label = "● " + label
|
||||||
}
|
}
|
||||||
return Button(label, fmt.Sprintf("%shour:%02d", dtpCallbackPrefix, hour))
|
return echotron.InlineKeyboardButton{
|
||||||
|
Text: label,
|
||||||
|
CallbackData: fmt.Sprintf("%shour:%02d", dtpCallbackPrefix, hour),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *DateTimePicker) minuteButton(minute int) echotron.InlineKeyboardButton {
|
func (s *DateTimePicker) minuteButton(minute int) echotron.InlineKeyboardButton {
|
||||||
label := fmt.Sprintf("%02d", minute)
|
label := fmt.Sprintf("%02d", minute)
|
||||||
if s.isMinuteDisabled(minute) {
|
if s.isMinuteDisabled(minute) {
|
||||||
return Button("-", "empty")
|
return echotron.InlineKeyboardButton{Text: "-", CallbackData: "empty"}
|
||||||
}
|
}
|
||||||
if s.selectedMinute != nil && *s.selectedMinute == minute {
|
if s.selectedMinute != nil && *s.selectedMinute == minute {
|
||||||
label = "● " + label
|
label = "● " + label
|
||||||
}
|
}
|
||||||
return Button(label, fmt.Sprintf("%smin:%02d", dtpCallbackPrefix, minute))
|
return echotron.InlineKeyboardButton{
|
||||||
|
Text: label,
|
||||||
|
CallbackData: fmt.Sprintf("%smin:%02d", dtpCallbackPrefix, minute),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *DateTimePicker) isHourDisabled(hour int) bool {
|
func (s *DateTimePicker) isHourDisabled(hour int) bool {
|
||||||
if s.selectedDate == nil {
|
if s.selectedDate == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
today := time.Now().In(mskLocation)
|
today := time.Now().In(MskLocation)
|
||||||
date := time.Date(s.selectedDate.Year(), s.selectedDate.Month(), s.selectedDate.Day(), 0, 0, 0, 0, mskLocation)
|
date := time.Date(s.selectedDate.Year(), s.selectedDate.Month(), s.selectedDate.Day(), 0, 0, 0, 0, MskLocation)
|
||||||
todayDate := time.Date(today.Year(), today.Month(), today.Day(), 0, 0, 0, 0, mskLocation)
|
todayDate := time.Date(today.Year(), today.Month(), today.Day(), 0, 0, 0, 0, MskLocation)
|
||||||
if date.After(todayDate) {
|
if date.After(todayDate) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -462,9 +475,9 @@ func (s *DateTimePicker) isMinuteDisabled(minute int) bool {
|
|||||||
if s.selectedDate == nil || s.selectedHour == nil {
|
if s.selectedDate == nil || s.selectedHour == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
today := time.Now().In(mskLocation)
|
today := time.Now().In(MskLocation)
|
||||||
date := time.Date(s.selectedDate.Year(), s.selectedDate.Month(), s.selectedDate.Day(), 0, 0, 0, 0, mskLocation)
|
date := time.Date(s.selectedDate.Year(), s.selectedDate.Month(), s.selectedDate.Day(), 0, 0, 0, 0, MskLocation)
|
||||||
todayDate := time.Date(today.Year(), today.Month(), today.Day(), 0, 0, 0, 0, mskLocation)
|
todayDate := time.Date(today.Year(), today.Month(), today.Day(), 0, 0, 0, 0, MskLocation)
|
||||||
if date.After(todayDate) {
|
if date.After(todayDate) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -475,7 +488,7 @@ func (s *DateTimePicker) isMinuteDisabled(minute int) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *DateTimePicker) prevMonth() {
|
func (s *DateTimePicker) prevMonth() {
|
||||||
now := time.Now().In(mskLocation)
|
now := time.Now().In(MskLocation)
|
||||||
if s.year == now.Year() && s.month == now.Month() {
|
if s.year == now.Year() && s.month == now.Month() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -497,15 +510,15 @@ func (s *DateTimePicker) nextMonth() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *DateTimePicker) monthPrevButton() echotron.InlineKeyboardButton {
|
func (s *DateTimePicker) monthPrevButton() echotron.InlineKeyboardButton {
|
||||||
now := time.Now().In(mskLocation)
|
now := time.Now().In(MskLocation)
|
||||||
if s.year == now.Year() && s.month == now.Month() {
|
if s.year == now.Year() && s.month == now.Month() {
|
||||||
return Button("-", "empty")
|
return echotron.InlineKeyboardButton{Text: "-", CallbackData: "empty"}
|
||||||
}
|
}
|
||||||
return Button("←", dtpCallbackPrefix+"month_prev")
|
return echotron.InlineKeyboardButton{Text: "←", CallbackData: dtpCallbackPrefix + "month_prev"}
|
||||||
}
|
}
|
||||||
|
|
||||||
func daysInMonth(year int, month time.Month) int {
|
func daysInMonth(year int, month time.Month) int {
|
||||||
return time.Date(year, month+1, 0, 0, 0, 0, 0, mskLocation).Day()
|
return time.Date(year, month+1, 0, 0, 0, 0, 0, MskLocation).Day()
|
||||||
}
|
}
|
||||||
|
|
||||||
func monthName(month time.Month) string {
|
func monthName(month time.Month) string {
|
||||||
@@ -598,10 +611,10 @@ func parseDateTimeInput(
|
|||||||
text = strings.ReplaceAll(text, ",", " ")
|
text = strings.ReplaceAll(text, ",", " ")
|
||||||
text = strings.ReplaceAll(text, " ", " ")
|
text = strings.ReplaceAll(text, " ", " ")
|
||||||
|
|
||||||
now := time.Now().In(mskLocation)
|
now := time.Now().In(MskLocation)
|
||||||
baseDate := now
|
baseDate := now
|
||||||
if currentDate != nil {
|
if currentDate != nil {
|
||||||
baseDate = currentDate.In(mskLocation)
|
baseDate = currentDate.In(MskLocation)
|
||||||
}
|
}
|
||||||
|
|
||||||
date, dateOk := parseDate(text, baseDate)
|
date, dateOk := parseDate(text, baseDate)
|
||||||
@@ -611,14 +624,14 @@ func parseDateTimeInput(
|
|||||||
return time.Time{}, false, false
|
return time.Time{}, false, false
|
||||||
}
|
}
|
||||||
if !dateOk {
|
if !dateOk {
|
||||||
date = time.Date(baseDate.Year(), baseDate.Month(), baseDate.Day(), 0, 0, 0, 0, mskLocation)
|
date = time.Date(baseDate.Year(), baseDate.Month(), baseDate.Day(), 0, 0, 0, 0, MskLocation)
|
||||||
}
|
}
|
||||||
if !timeOk {
|
if !timeOk {
|
||||||
hour = 0
|
hour = 0
|
||||||
minute = 0
|
minute = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
value := time.Date(date.Year(), date.Month(), date.Day(), hour, minute, 0, 0, mskLocation)
|
value := time.Date(date.Year(), date.Month(), date.Day(), hour, minute, 0, 0, MskLocation)
|
||||||
if value.Before(now) {
|
if value.Before(now) {
|
||||||
return time.Time{}, false, false
|
return time.Time{}, false, false
|
||||||
}
|
}
|
||||||
@@ -658,7 +671,7 @@ func parseNumericDate(text string) time.Time {
|
|||||||
if match := reDMY.FindStringSubmatch(text); len(match) >= 3 {
|
if match := reDMY.FindStringSubmatch(text); len(match) >= 3 {
|
||||||
day := parseInt(match[1])
|
day := parseInt(match[1])
|
||||||
month := parseInt(match[2])
|
month := parseInt(match[2])
|
||||||
year := time.Now().In(mskLocation).Year()
|
year := time.Now().In(MskLocation).Year()
|
||||||
if len(match) == 4 && match[3] != "" {
|
if len(match) == 4 && match[3] != "" {
|
||||||
year = parseInt(match[3])
|
year = parseInt(match[3])
|
||||||
if year < 100 {
|
if year < 100 {
|
||||||
@@ -705,7 +718,7 @@ func parseWordDate(text string) time.Time {
|
|||||||
return time.Time{}
|
return time.Time{}
|
||||||
}
|
}
|
||||||
|
|
||||||
year := time.Now().In(mskLocation).Year()
|
year := time.Now().In(MskLocation).Year()
|
||||||
if len(match) == 4 && match[3] != "" {
|
if len(match) == 4 && match[3] != "" {
|
||||||
year = parseInt(match[3])
|
year = parseInt(match[3])
|
||||||
if year < 100 {
|
if year < 100 {
|
||||||
@@ -772,10 +785,10 @@ func safeDate(year, month, day int) time.Time {
|
|||||||
if day < 1 || day > 31 {
|
if day < 1 || day > 31 {
|
||||||
return time.Time{}
|
return time.Time{}
|
||||||
}
|
}
|
||||||
return time.Date(year, time.Month(month), day, 0, 0, 0, 0, mskLocation)
|
return time.Date(year, time.Month(month), day, 0, 0, 0, 0, MskLocation)
|
||||||
}
|
}
|
||||||
|
|
||||||
func weekdayName(day time.Weekday) string {
|
func WeekdayName(day time.Weekday) string {
|
||||||
switch day {
|
switch day {
|
||||||
case time.Monday:
|
case time.Monday:
|
||||||
return "Пн"
|
return "Пн"
|
||||||
@@ -797,12 +810,12 @@ func weekdayName(day time.Weekday) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func formatCompactDateTime(value time.Time) string {
|
func formatCompactDateTime(value time.Time) string {
|
||||||
weekday := weekdayName(value.Weekday())
|
weekday := WeekdayName(value.Weekday())
|
||||||
month := monthShort(value.Month())
|
month := MonthShort(value.Month())
|
||||||
return fmt.Sprintf("%s %02d %s %s", weekday, value.Day(), month, value.Format("15:04"))
|
return fmt.Sprintf("%s %02d %s %s", weekday, value.Day(), month, value.Format("15:04"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func monthShort(month time.Month) string {
|
func MonthShort(month time.Month) string {
|
||||||
months := []string{
|
months := []string{
|
||||||
"янв", "фев", "мар", "апр", "май", "июн",
|
"янв", "фев", "мар", "апр", "май", "июн",
|
||||||
"июл", "авг", "сен", "окт", "ноя", "дек",
|
"июл", "авг", "сен", "окт", "ноя", "дек",
|
||||||
Reference in New Issue
Block a user