- Split 1571-line handlers.go into: handlers.go (core), clock.go, settings.go, calendar.go, report.go - Redesigned export month picker: year navigation + 12-month grid instead of prev/next month - Fixed calendar last row: pad remaining cells with empty buttons to ensure 7 columns - Added Go doc comments across all files (dateutil.go, totals.go, store.go, main.go, webhook.go)
150 lines
4.1 KiB
Go
150 lines
4.1 KiB
Go
package bot
|
|
|
|
import (
|
|
"fmt"
|
|
"log/slog"
|
|
"time"
|
|
|
|
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
|
|
|
"worktimeBot/internal/db"
|
|
)
|
|
|
|
// buildStatusText returns the main menu status text for the current user.
|
|
func (h *Handler) buildStatusText(chatID int64) string {
|
|
user, day, err := h.getUserToday(chatID)
|
|
if err != nil {
|
|
return "Welcome. Choose an action:"
|
|
}
|
|
|
|
text := fmt.Sprintf("Today: %s", formatDateForCalendar(day.Date, user.Calendar))
|
|
|
|
if day.IsDayOff {
|
|
text += "\nDay Off"
|
|
return text + "\n\nChoose an action:"
|
|
}
|
|
|
|
wt, _ := h.DB.GetWorkType(day.CurrentWorkTypeID)
|
|
if wt != nil {
|
|
text += fmt.Sprintf("\nWork type: %s", wt.Name)
|
|
}
|
|
|
|
last, err := h.DB.GetLastEvent(user.ID)
|
|
if err == nil && last != nil && last.EventType == "in" {
|
|
loc := loadLocation(user.Timezone)
|
|
t := time.Unix(last.OccurredAt, 0).In(loc).Format("15:04")
|
|
text += fmt.Sprintf("\nStatus: working since %s", t)
|
|
} else {
|
|
text += "\nStatus: not working"
|
|
}
|
|
|
|
events, err := h.DB.EventsForDayByDayID(day.ID)
|
|
if err == nil && len(events) > 0 {
|
|
loc := loadLocation(user.Timezone)
|
|
now := time.Now().In(loc)
|
|
y, m, d := now.Date()
|
|
dayStart := time.Date(y, m, d, 0, 0, 0, 0, loc).Unix()
|
|
dayEnd := now.Unix()
|
|
totals := ComputeDailyTotals(events, day.MinBreakThreshold, dayStart, dayEnd)
|
|
text += fmt.Sprintf("\nToday: %s worked", formatDuration(totals.TotalSeconds))
|
|
}
|
|
|
|
text += "\n\nChoose an action:"
|
|
return text
|
|
}
|
|
|
|
// backToMenu returns to the main menu from any sub-menu.
|
|
func (h *Handler) backToMenu(chatID int64, msgID int, callbackID string) {
|
|
defer h.Bot.Request(tgbotapi.NewCallback(callbackID, ""))
|
|
text := h.buildStatusText(chatID)
|
|
edit := tgbotapi.NewEditMessageText(chatID, msgID, text)
|
|
kb := mainKeyboard()
|
|
edit.ReplyMarkup = &kb
|
|
h.Bot.Send(edit)
|
|
}
|
|
|
|
// buildDailyReport builds a formatted daily report string for a given user/day.
|
|
func (h *Handler) buildDailyReport(user *db.User, day *db.Day, events []db.Event) string {
|
|
loc, err := time.LoadLocation(user.Timezone)
|
|
if err != nil {
|
|
loc = time.UTC
|
|
}
|
|
|
|
if len(events) == 0 {
|
|
if day.IsDayOff {
|
|
return fmt.Sprintf("%s - Day Off", formatDateForCalendar(day.Date, user.Calendar))
|
|
}
|
|
return fmt.Sprintf("%s - No events recorded.", formatDateForCalendar(day.Date, user.Calendar))
|
|
}
|
|
|
|
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 {
|
|
lines += "\nDay Off: Yes"
|
|
}
|
|
lines += "\n\n"
|
|
|
|
for _, e := range events {
|
|
t := time.Unix(e.OccurredAt, 0).In(loc).Format("15:04")
|
|
label := "IN"
|
|
if e.EventType == "out" {
|
|
label = "OUT"
|
|
}
|
|
suffix := ""
|
|
if e.WorkTypeID != nil {
|
|
if wtObj, err := h.DB.GetWorkType(*e.WorkTypeID); err == nil {
|
|
suffix = " [" + wtObj.Name + "]"
|
|
}
|
|
}
|
|
if e.Note != "" {
|
|
suffix += " (" + e.Note + ")"
|
|
}
|
|
lines += fmt.Sprintf("%s %s%s\n", label, t, suffix)
|
|
}
|
|
|
|
lines += fmt.Sprintf("\nSummary:\n Work: %s\n Break: %s\n",
|
|
formatDuration(totals.TotalSeconds),
|
|
formatDuration(totals.BreakSeconds))
|
|
|
|
return lines
|
|
}
|
|
|
|
// SendDailyReport sends the automated daily report to the user.
|
|
func (h *Handler) SendDailyReport(userID int64, chatID int64) {
|
|
user, err := h.DB.GetUserByChatID(chatID)
|
|
if err != nil {
|
|
slog.Error("daily report: user not found", "user_id", userID)
|
|
return
|
|
}
|
|
loc, err := time.LoadLocation(user.Timezone)
|
|
if err != nil {
|
|
loc = time.UTC
|
|
}
|
|
now := time.Now().In(loc)
|
|
today := now.Format("2006-01-02")
|
|
|
|
day, err := h.DB.GetOrCreateDay(user.ID, today)
|
|
if err != nil {
|
|
slog.Error("daily report: get day", "error", err)
|
|
return
|
|
}
|
|
events, err := h.DB.EventsForDayByDayID(day.ID)
|
|
if err != nil {
|
|
slog.Error("daily report: get events", "error", err)
|
|
return
|
|
}
|
|
text := h.buildDailyReport(user, day, events)
|
|
reply := tgbotapi.NewMessage(chatID, text)
|
|
h.Bot.Send(reply)
|
|
h.DB.MarkReportSent(user.ID, today)
|
|
}
|