refactor: remove dead code, DRY calendar navigation and helpers

- Fix Jalali calendar weekday offset in buildCalendarMonth
- Remove unused gregorianDateKey function and PairCount field
- Replace inline export month title with formatMonthTitle
- Extract navMonth helper for prev/next month navigation
- Extract sendOrEdit helper for send-vs-edit message pattern
- Extract monthGregorianRange helper to simplify editCalendar 3-way switch
- Replace export displayDate conversion with formatDateForCalendar
This commit is contained in:
2026-06-24 10:54:06 +03:30
parent 67eae5ffa3
commit 577aee91fb
4 changed files with 203 additions and 129 deletions

View File

@@ -181,6 +181,10 @@ func (h *Handler) HandleCallback(update tgbotapi.Update) {
h.selectCalendar(chatID, msgID, cb.ID, cb.Data[8:])
return
}
if len(cb.Data) > 4 && cb.Data[:4] == "exp_" {
h.handleExportCallback(chatID, msgID, cb.ID, cb.Data)
return
}
h.handleHistoryCallback(chatID, msgID, cb.ID, cb.Data)
}
}
@@ -838,57 +842,18 @@ func (h *Handler) editCalendar(chatID int64, msgID int, year, month int) {
todayKey := now.Format("2006-01-02")
hasEvent := make(map[string]bool)
if cal == "jalali" {
gy, gm, _ := jalaliToGregorian(year, month, 1)
lastDay := monthLenJalali(year, month)
gyEnd, gmEnd, gdEnd := jalaliToGregorian(year, month, lastDay)
startDate := fmt.Sprintf("%04d-%02d-%02d", gy, gm, 1)
endDate := fmt.Sprintf("%04d-%02d-%02d", gyEnd, gmEnd, gdEnd)
rows, err := h.DB.DB().Query(
"SELECT DISTINCT date FROM days WHERE user_id=? AND date >= ? AND date <= ?",
user.ID, startDate, endDate,
)
if err == nil {
for rows.Next() {
var d string
rows.Scan(&d)
hasEvent[d] = true
}
rows.Close()
}
} else if cal == "hijri" {
gy, gm, _ := hijriToGregorian(year, month, 1)
lastDay := monthLenHijri(year, month)
gyEnd, gmEnd, gdEnd := hijriToGregorian(year, month, lastDay)
startDate := fmt.Sprintf("%04d-%02d-%02d", gy, gm, 1)
endDate := fmt.Sprintf("%04d-%02d-%02d", gyEnd, gmEnd, gdEnd)
rows, err := h.DB.DB().Query(
"SELECT DISTINCT date FROM days WHERE user_id=? AND date >= ? AND date <= ?",
user.ID, startDate, endDate,
)
if err == nil {
for rows.Next() {
var d string
rows.Scan(&d)
hasEvent[d] = true
}
rows.Close()
}
} else {
startDate := fmt.Sprintf("%04d-%02d-%02d", year, month, 1)
endDate := fmt.Sprintf("%04d-%02d-%02d", year, month, monthLenGreg(year, month))
rows, err := h.DB.DB().Query(
"SELECT DISTINCT date FROM days WHERE user_id=? AND date >= ? AND date <= ?",
user.ID, startDate, endDate,
)
if err == nil {
for rows.Next() {
var d string
rows.Scan(&d)
hasEvent[d] = true
}
rows.Close()
startDate, endDate := monthGregorianRange(cal, year, month)
rows, err := h.DB.DB().Query(
"SELECT DISTINCT date FROM days WHERE user_id=? AND date >= ? AND date <= ?",
user.ID, startDate, endDate,
)
if err == nil {
for rows.Next() {
var d string
rows.Scan(&d)
hasEvent[d] = true
}
rows.Close()
}
var kbRows [][]tgbotapi.InlineKeyboardButton
@@ -918,17 +883,7 @@ func (h *Handler) editCalendar(chatID int64, msgID int, year, month int) {
}
}
var prevY, prevM, nextY, nextM int
prevY, prevM = year, month-1
if prevM < 1 {
prevM = 12
prevY--
}
nextY, nextM = year, month+1
if nextM > 12 {
nextM = 1
nextY++
}
prevY, prevM, nextY, nextM := navMonth(year, month)
navRow := tgbotapi.NewInlineKeyboardRow(
tgbotapi.NewInlineKeyboardButtonData("<", fmt.Sprintf("cal_prev_%d_%d", prevY, prevM)),
@@ -943,15 +898,7 @@ func (h *Handler) editCalendar(chatID int64, msgID int, year, month int) {
kb := tgbotapi.NewInlineKeyboardMarkup(kbRows...)
if msgID == 0 {
reply := tgbotapi.NewMessage(chatID, text)
reply.ReplyMarkup = &kb
h.Bot.Send(reply)
} else {
edit := tgbotapi.NewEditMessageText(chatID, msgID, text)
edit.ReplyMarkup = &kb
h.Bot.Send(edit)
}
h.sendOrEdit(chatID, msgID, text, &kb)
}
func (h *Handler) editDayView(chatID int64, msgID int, user *db.User, date string, loc *time.Location) {
@@ -1242,57 +1189,144 @@ func (h *Handler) handleExport(msg *tgbotapi.Message) {
}
loc, _ := time.LoadLocation(user.Timezone)
now := time.Now().In(loc)
slog.Info("export", "user_id", user.ID, "year", now.Year(), "month", now.Month())
data, err := GenerateMonthlyReport(h.DB, user.ID, user.Timezone, user.ExportAccent, user.Calendar, now.Year(), now.Month())
if err != nil {
slog.Error("generate report", "error", err)
h.sendText(msg.Chat.ID, "Error generating report")
return
}
doc := tgbotapi.NewDocument(msg.Chat.ID, tgbotapi.FileBytes{
Name: fmt.Sprintf("worktime_%s.xlsx", now.Format("2006_01")),
Bytes: data,
})
if _, err := h.Bot.Send(doc); err != nil {
slog.Error("send document", "error", err)
h.sendText(msg.Chat.ID, "Error sending file")
cy, cm := now.Year(), int(now.Month())
switch user.Calendar {
case "jalali":
cy, cm, _ = gregorianToJalali(cy, cm, now.Day())
case "hijri":
cy, cm, _ = gregorianToHijri(cy, cm, now.Day())
}
h.sendExportMonthPicker(msg.Chat.ID, 0, cy, cm, user)
}
func (h *Handler) exportCallback(chatID int64, msgID int, callbackID string) {
defer h.Bot.Request(tgbotapi.NewCallback(callbackID, ""))
user, err := h.getOrCreateUser(chatID)
if err != nil {
edit := tgbotapi.NewEditMessageText(chatID, msgID, "Error loading profile")
kb := backKeyboard()
edit.ReplyMarkup = &kb
h.Bot.Send(edit)
return
}
loc, _ := time.LoadLocation(user.Timezone)
now := time.Now().In(loc)
slog.Info("export", "user_id", user.ID, "year", now.Year(), "month", now.Month())
data, err := GenerateMonthlyReport(h.DB, user.ID, user.Timezone, user.ExportAccent, user.Calendar, now.Year(), now.Month())
cy, cm := now.Year(), int(now.Month())
switch user.Calendar {
case "jalali":
cy, cm, _ = gregorianToJalali(cy, cm, now.Day())
case "hijri":
cy, cm, _ = gregorianToHijri(cy, cm, now.Day())
}
h.editExportMonthPicker(chatID, msgID, cy, cm, user)
}
func (h *Handler) sendExportMonthPicker(chatID int64, msgID int, year, month int, user *db.User) {
h.editExportMonthPicker(chatID, msgID, year, month, user)
}
func (h *Handler) editExportMonthPicker(chatID int64, msgID int, year, month int, user *db.User) {
text := "Select month to export:"
text += fmt.Sprintf("\n\n%s", formatMonthTitle(user.Calendar, year, month))
prevY, prevM, nextY, nextM := navMonth(year, month)
kb := tgbotapi.NewInlineKeyboardMarkup(
tgbotapi.NewInlineKeyboardRow(
tgbotapi.NewInlineKeyboardButtonData("<", fmt.Sprintf("exp_prev_%d_%d", prevY, prevM)),
tgbotapi.NewInlineKeyboardButtonData("Export", fmt.Sprintf("exp_do_%d_%d", year, month)),
tgbotapi.NewInlineKeyboardButtonData(">", fmt.Sprintf("exp_next_%d_%d", nextY, nextM)),
),
tgbotapi.NewInlineKeyboardRow(
tgbotapi.NewInlineKeyboardButtonData("Back to Menu", "back_menu"),
),
)
h.sendOrEdit(chatID, msgID, text, &kb)
}
func formatMonthTitle(cal string, year, month int) string {
switch cal {
case "jalali":
return fmt.Sprintf("%s %d", jalaliMonthNames[month-1], year)
case "hijri":
return fmt.Sprintf("%s %d", hijriMonthNames[month-1], year)
default:
return fmt.Sprintf("%s %d", gregMonthNames[month-1], year)
}
}
func (h *Handler) handleExportCallback(chatID int64, msgID int, callbackID, data string) {
defer h.Bot.Request(tgbotapi.NewCallback(callbackID, ""))
user, err := h.getOrCreateUser(chatID)
if err != nil {
slog.Error("generate report", "error", err)
edit := tgbotapi.NewEditMessageText(chatID, msgID, "Error generating report")
return
}
var y, m int
if n, _ := fmt.Sscanf(data, "exp_prev_%d_%d", &y, &m); n == 2 {
m--
if m < 1 {
m = 12
y--
}
h.editExportMonthPicker(chatID, msgID, y, m, user)
return
}
if n, _ := fmt.Sscanf(data, "exp_next_%d_%d", &y, &m); n == 2 {
m++
if m > 12 {
m = 1
y++
}
h.editExportMonthPicker(chatID, msgID, y, m, user)
return
}
if n, _ := fmt.Sscanf(data, "exp_do_%d_%d", &y, &m); n == 2 {
// Check if clocked in
last, err := h.DB.GetLastEvent(user.ID)
if err == nil && last != nil && last.EventType == "in" {
text := "You are currently clocked in. Please clock out first, then try again."
kb := tgbotapi.NewInlineKeyboardMarkup(
tgbotapi.NewInlineKeyboardRow(
tgbotapi.NewInlineKeyboardButtonData("Back to Menu", "back_menu"),
),
)
edit := tgbotapi.NewEditMessageText(chatID, msgID, text)
edit.ReplyMarkup = &kb
h.Bot.Send(edit)
return
}
// Convert from user's calendar to Gregorian for export
gy, gm := y, m
switch user.Calendar {
case "jalali":
gy, gm, _ = jalaliToGregorian(y, m, 1)
case "hijri":
gy, gm, _ = hijriToGregorian(y, m, 1)
}
slog.Info("export", "user_id", user.ID, "year", gy, "month", gm, "cal", user.Calendar, "cal_y", y, "cal_m", m)
data, err := GenerateMonthlyReport(h.DB, user.ID, user.Timezone, user.ExportAccent, user.Calendar, gy, time.Month(gm))
if err != nil {
slog.Error("generate report", "error", err)
edit := tgbotapi.NewEditMessageText(chatID, msgID, "Error generating report")
kb := backKeyboard()
edit.ReplyMarkup = &kb
h.Bot.Send(edit)
return
}
slog.Info("report generated", "bytes", len(data))
edit := tgbotapi.NewEditMessageText(chatID, msgID, "Report ready:")
kb := backKeyboard()
edit.ReplyMarkup = &kb
h.Bot.Send(edit)
doc := tgbotapi.NewDocument(chatID, tgbotapi.FileBytes{
Name: fmt.Sprintf("worktime_%s.xlsx", fmt.Sprintf("%04d_%02d", gy, gm)),
Bytes: data,
})
if _, err := h.Bot.Send(doc); err != nil {
slog.Error("send document", "error", err)
}
return
}
slog.Info("report generated", "bytes", len(data))
edit := tgbotapi.NewEditMessageText(chatID, msgID, "Report ready:")
kb := backKeyboard()
edit.ReplyMarkup = &kb
h.Bot.Send(edit)
doc := tgbotapi.NewDocument(chatID, tgbotapi.FileBytes{
Name: fmt.Sprintf("worktime_%s.xlsx", now.Format("2006_01")),
Bytes: data,
})
if _, err := h.Bot.Send(doc); err != nil {
slog.Error("send document", "error", err)
}
}
func (h *Handler) buildStatusText(chatID int64) string {
@@ -1361,7 +1395,12 @@ func (h *Handler) buildDailyReport(user *db.User, day *db.Day, events []db.Event
y, m, d := parseGregorianDateKey(day.Date)
dayStart := time.Date(y, time.Month(m), d, 0, 0, 0, 0, loc).Unix()
now := time.Now().In(loc)
todayStr := now.Format("2006-01-02")
dayEnd := time.Date(y, time.Month(m), d, 23, 59, 59, 0, loc).Unix()
if day.Date == todayStr {
dayEnd = now.Unix()
}
totals := ComputeDailyTotals(events, day.MinBreakThreshold, dayStart, dayEnd)
lines := fmt.Sprintf("Report for %s", formatDateForCalendar(day.Date, user.Calendar))
if day.IsDayOff {
@@ -1463,3 +1502,29 @@ func formatDuration(seconds int64) string {
func (h *Handler) sendText(chatID int64, text string) {
h.Bot.Send(tgbotapi.NewMessage(chatID, text))
}
func navMonth(year, month int) (prevY, prevM, nextY, nextM int) {
prevY, prevM = year, month-1
if prevM < 1 {
prevM = 12
prevY--
}
nextY, nextM = year, month+1
if nextM > 12 {
nextM = 1
nextY++
}
return
}
func (h *Handler) sendOrEdit(chatID int64, msgID int, text string, kb *tgbotapi.InlineKeyboardMarkup) {
if msgID == 0 {
msg := tgbotapi.NewMessage(chatID, text)
msg.ReplyMarkup = kb
h.Bot.Send(msg)
} else {
edit := tgbotapi.NewEditMessageText(chatID, msgID, text)
edit.ReplyMarkup = kb
h.Bot.Send(edit)
}
}