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:
@@ -211,9 +211,12 @@ func buildCalendarMonth(cal string, year, month int) calMonth {
|
|||||||
|
|
||||||
gy, gm, gd := jalaliToGregorian(year, month, 1)
|
gy, gm, gd := jalaliToGregorian(year, month, 1)
|
||||||
dow := dayOfWeekGregorian(gy, gm, gd)
|
dow := dayOfWeekGregorian(gy, gm, gd)
|
||||||
_ = dow
|
startOffset := (dow + 1) % 7
|
||||||
|
|
||||||
var days []calDay
|
var days []calDay
|
||||||
|
for i := 0; i < startOffset; i++ {
|
||||||
|
days = append(days, calDay{dayNum: 0, date: ""})
|
||||||
|
}
|
||||||
for d := 1; d <= totalDays; d++ {
|
for d := 1; d <= totalDays; d++ {
|
||||||
gy, gm, gd = jalaliToGregorian(year, month, d)
|
gy, gm, gd = jalaliToGregorian(year, month, d)
|
||||||
days = append(days, calDay{
|
days = append(days, calDay{
|
||||||
@@ -267,10 +270,6 @@ func buildCalendarMonth(cal string, year, month int) calMonth {
|
|||||||
return calMonth{title: title, weekDays: weekDays, days: days}
|
return calMonth{title: title, weekDays: weekDays, days: days}
|
||||||
}
|
}
|
||||||
|
|
||||||
func gregorianDateKey(year, month, day int) string {
|
|
||||||
return fmt.Sprintf("%04d-%02d-%02d", year, month, day)
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseGregorianDateKey(key string) (int, int, int) {
|
func parseGregorianDateKey(key string) (int, int, int) {
|
||||||
var y, m, d int
|
var y, m, d int
|
||||||
fmt.Sscanf(key, "%04d-%02d-%02d", &y, &m, &d)
|
fmt.Sscanf(key, "%04d-%02d-%02d", &y, &m, &d)
|
||||||
@@ -290,3 +289,27 @@ func formatDateForCalendar(date, calType string) string {
|
|||||||
return date
|
return date
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// monthGregorianRange returns the Gregorian start and end date strings
|
||||||
|
// for a given calendar month (cal=gregorian|jalali|hijri).
|
||||||
|
func monthGregorianRange(cal string, year, month int) (start, end string) {
|
||||||
|
switch cal {
|
||||||
|
case "jalali":
|
||||||
|
gy, gm, gd := jalaliToGregorian(year, month, 1)
|
||||||
|
start = fmt.Sprintf("%04d-%02d-%02d", gy, gm, gd)
|
||||||
|
lastDay := monthLenJalali(year, month)
|
||||||
|
gy, gm, gd = jalaliToGregorian(year, month, lastDay)
|
||||||
|
end = fmt.Sprintf("%04d-%02d-%02d", gy, gm, gd)
|
||||||
|
case "hijri":
|
||||||
|
gy, gm, gd := hijriToGregorian(year, month, 1)
|
||||||
|
start = fmt.Sprintf("%04d-%02d-%02d", gy, gm, gd)
|
||||||
|
lastDay := monthLenHijri(year, month)
|
||||||
|
gy, gm, gd = hijriToGregorian(year, month, lastDay)
|
||||||
|
end = fmt.Sprintf("%04d-%02d-%02d", gy, gm, gd)
|
||||||
|
default:
|
||||||
|
start = fmt.Sprintf("%04d-%02d-%02d", year, month, 1)
|
||||||
|
lastDay := monthLenGreg(year, month)
|
||||||
|
end = fmt.Sprintf("%04d-%02d-%02d", year, month, lastDay)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|||||||
@@ -76,17 +76,16 @@ func GenerateMonthlyReport(store *db.Store, userID int64, timezone, accent, cale
|
|||||||
Alignment: &excelize.Alignment{Horizontal: "center", Vertical: "center"},
|
Alignment: &excelize.Alignment{Horizontal: "center", Vertical: "center"},
|
||||||
})
|
})
|
||||||
|
|
||||||
var monthName string
|
var jy, jm int
|
||||||
switch calendar {
|
switch calendar {
|
||||||
case "jalali":
|
case "jalali":
|
||||||
jy, jm, _ := gregorianToJalali(year, int(month), 1)
|
jy, jm, _ = gregorianToJalali(year, int(month), 1)
|
||||||
monthName = fmt.Sprintf("%s %d", jalaliMonthNames[jm-1], jy)
|
|
||||||
case "hijri":
|
case "hijri":
|
||||||
hy, hm, _ := gregorianToHijri(year, int(month), 1)
|
jy, jm, _ = gregorianToHijri(year, int(month), 1)
|
||||||
monthName = fmt.Sprintf("%s %d", hijriMonthNames[hm-1], hy)
|
|
||||||
default:
|
default:
|
||||||
monthName = time.Date(year, month, 1, 0, 0, 0, 0, time.UTC).Format("January 2006")
|
jy, jm = year, int(month)
|
||||||
}
|
}
|
||||||
|
monthName := formatMonthTitle(calendar, jy, jm)
|
||||||
f.SetCellValue(sheet, "A1", fmt.Sprintf("Work Time Report - %s", monthName))
|
f.SetCellValue(sheet, "A1", fmt.Sprintf("Work Time Report - %s", monthName))
|
||||||
f.MergeCell(sheet, "A1", "F1")
|
f.MergeCell(sheet, "A1", "F1")
|
||||||
f.SetCellStyle(sheet, "A1", "F1", titleStyle)
|
f.SetCellStyle(sheet, "A1", "F1", titleStyle)
|
||||||
@@ -130,16 +129,7 @@ func GenerateMonthlyReport(store *db.Store, userID int64, timezone, accent, cale
|
|||||||
|
|
||||||
for d := firstDay; !d.After(lastDay); d = d.AddDate(0, 0, 1) {
|
for d := firstDay; !d.After(lastDay); d = d.AddDate(0, 0, 1) {
|
||||||
dateStr := d.Format("2006-01-02")
|
dateStr := d.Format("2006-01-02")
|
||||||
displayDate := dateStr
|
displayDate := formatDateForCalendar(dateStr, calendar)
|
||||||
if calendar == "jalali" {
|
|
||||||
gy, gm, gd := parseGregorianDateKey(dateStr)
|
|
||||||
jy, jm, jd := gregorianToJalali(gy, gm, gd)
|
|
||||||
displayDate = fmt.Sprintf("%04d-%02d-%02d", jy, jm, jd)
|
|
||||||
} else if calendar == "hijri" {
|
|
||||||
gy, gm, gd := parseGregorianDateKey(dateStr)
|
|
||||||
hy, hm, hd := gregorianToHijri(gy, gm, gd)
|
|
||||||
displayDate = fmt.Sprintf("%04d-%02d-%02d", hy, hm, hd)
|
|
||||||
}
|
|
||||||
|
|
||||||
day, exists := dayMap[dateStr]
|
day, exists := dayMap[dateStr]
|
||||||
if !exists {
|
if !exists {
|
||||||
|
|||||||
@@ -181,6 +181,10 @@ func (h *Handler) HandleCallback(update tgbotapi.Update) {
|
|||||||
h.selectCalendar(chatID, msgID, cb.ID, cb.Data[8:])
|
h.selectCalendar(chatID, msgID, cb.ID, cb.Data[8:])
|
||||||
return
|
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)
|
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")
|
todayKey := now.Format("2006-01-02")
|
||||||
|
|
||||||
hasEvent := make(map[string]bool)
|
hasEvent := make(map[string]bool)
|
||||||
if cal == "jalali" {
|
startDate, endDate := monthGregorianRange(cal, year, month)
|
||||||
gy, gm, _ := jalaliToGregorian(year, month, 1)
|
rows, err := h.DB.DB().Query(
|
||||||
lastDay := monthLenJalali(year, month)
|
"SELECT DISTINCT date FROM days WHERE user_id=? AND date >= ? AND date <= ?",
|
||||||
gyEnd, gmEnd, gdEnd := jalaliToGregorian(year, month, lastDay)
|
user.ID, startDate, endDate,
|
||||||
startDate := fmt.Sprintf("%04d-%02d-%02d", gy, gm, 1)
|
)
|
||||||
endDate := fmt.Sprintf("%04d-%02d-%02d", gyEnd, gmEnd, gdEnd)
|
if err == nil {
|
||||||
rows, err := h.DB.DB().Query(
|
for rows.Next() {
|
||||||
"SELECT DISTINCT date FROM days WHERE user_id=? AND date >= ? AND date <= ?",
|
var d string
|
||||||
user.ID, startDate, endDate,
|
rows.Scan(&d)
|
||||||
)
|
hasEvent[d] = true
|
||||||
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()
|
|
||||||
}
|
}
|
||||||
|
rows.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
var kbRows [][]tgbotapi.InlineKeyboardButton
|
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, nextY, nextM := navMonth(year, month)
|
||||||
prevY, prevM = year, month-1
|
|
||||||
if prevM < 1 {
|
|
||||||
prevM = 12
|
|
||||||
prevY--
|
|
||||||
}
|
|
||||||
nextY, nextM = year, month+1
|
|
||||||
if nextM > 12 {
|
|
||||||
nextM = 1
|
|
||||||
nextY++
|
|
||||||
}
|
|
||||||
|
|
||||||
navRow := tgbotapi.NewInlineKeyboardRow(
|
navRow := tgbotapi.NewInlineKeyboardRow(
|
||||||
tgbotapi.NewInlineKeyboardButtonData("<", fmt.Sprintf("cal_prev_%d_%d", prevY, prevM)),
|
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...)
|
kb := tgbotapi.NewInlineKeyboardMarkup(kbRows...)
|
||||||
|
|
||||||
if msgID == 0 {
|
h.sendOrEdit(chatID, msgID, text, &kb)
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) editDayView(chatID int64, msgID int, user *db.User, date string, loc *time.Location) {
|
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)
|
loc, _ := time.LoadLocation(user.Timezone)
|
||||||
now := time.Now().In(loc)
|
now := time.Now().In(loc)
|
||||||
slog.Info("export", "user_id", user.ID, "year", now.Year(), "month", now.Month())
|
cy, cm := now.Year(), int(now.Month())
|
||||||
data, err := GenerateMonthlyReport(h.DB, user.ID, user.Timezone, user.ExportAccent, user.Calendar, now.Year(), now.Month())
|
switch user.Calendar {
|
||||||
if err != nil {
|
case "jalali":
|
||||||
slog.Error("generate report", "error", err)
|
cy, cm, _ = gregorianToJalali(cy, cm, now.Day())
|
||||||
h.sendText(msg.Chat.ID, "Error generating report")
|
case "hijri":
|
||||||
return
|
cy, cm, _ = gregorianToHijri(cy, cm, now.Day())
|
||||||
}
|
|
||||||
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")
|
|
||||||
}
|
}
|
||||||
|
h.sendExportMonthPicker(msg.Chat.ID, 0, cy, cm, user)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) exportCallback(chatID int64, msgID int, callbackID string) {
|
func (h *Handler) exportCallback(chatID int64, msgID int, callbackID string) {
|
||||||
defer h.Bot.Request(tgbotapi.NewCallback(callbackID, ""))
|
defer h.Bot.Request(tgbotapi.NewCallback(callbackID, ""))
|
||||||
user, err := h.getOrCreateUser(chatID)
|
user, err := h.getOrCreateUser(chatID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
edit := tgbotapi.NewEditMessageText(chatID, msgID, "Error loading profile")
|
|
||||||
kb := backKeyboard()
|
|
||||||
edit.ReplyMarkup = &kb
|
|
||||||
h.Bot.Send(edit)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
loc, _ := time.LoadLocation(user.Timezone)
|
loc, _ := time.LoadLocation(user.Timezone)
|
||||||
now := time.Now().In(loc)
|
now := time.Now().In(loc)
|
||||||
slog.Info("export", "user_id", user.ID, "year", now.Year(), "month", now.Month())
|
cy, cm := now.Year(), int(now.Month())
|
||||||
data, err := GenerateMonthlyReport(h.DB, user.ID, user.Timezone, user.ExportAccent, user.Calendar, now.Year(), 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 {
|
if err != nil {
|
||||||
slog.Error("generate report", "error", err)
|
return
|
||||||
edit := tgbotapi.NewEditMessageText(chatID, msgID, "Error generating report")
|
}
|
||||||
|
|
||||||
|
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()
|
kb := backKeyboard()
|
||||||
edit.ReplyMarkup = &kb
|
edit.ReplyMarkup = &kb
|
||||||
h.Bot.Send(edit)
|
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
|
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 {
|
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)
|
y, m, d := parseGregorianDateKey(day.Date)
|
||||||
dayStart := time.Date(y, time.Month(m), d, 0, 0, 0, 0, loc).Unix()
|
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()
|
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)
|
totals := ComputeDailyTotals(events, day.MinBreakThreshold, dayStart, dayEnd)
|
||||||
lines := fmt.Sprintf("Report for %s", formatDateForCalendar(day.Date, user.Calendar))
|
lines := fmt.Sprintf("Report for %s", formatDateForCalendar(day.Date, user.Calendar))
|
||||||
if day.IsDayOff {
|
if day.IsDayOff {
|
||||||
@@ -1463,3 +1502,29 @@ func formatDuration(seconds int64) string {
|
|||||||
func (h *Handler) sendText(chatID int64, text string) {
|
func (h *Handler) sendText(chatID int64, text string) {
|
||||||
h.Bot.Send(tgbotapi.NewMessage(chatID, text))
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ const (
|
|||||||
type DailyTotals struct {
|
type DailyTotals struct {
|
||||||
TotalSeconds int64
|
TotalSeconds int64
|
||||||
BreakSeconds int64
|
BreakSeconds int64
|
||||||
PairCount int
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func ComputeDailyTotals(events []db.Event, minBreakThreshold int64, dayStart, dayEnd int64) DailyTotals {
|
func ComputeDailyTotals(events []db.Event, minBreakThreshold int64, dayStart, dayEnd int64) DailyTotals {
|
||||||
@@ -41,7 +40,6 @@ func ComputeDailyTotals(events []db.Event, minBreakThreshold int64, dayStart, da
|
|||||||
|
|
||||||
var workTotal, breakTotal int64
|
var workTotal, breakTotal int64
|
||||||
var workStart, breakStart int64
|
var workStart, breakStart int64
|
||||||
pairs := 0
|
|
||||||
state := StateIdle
|
state := StateIdle
|
||||||
|
|
||||||
for _, e := range sorted {
|
for _, e := range sorted {
|
||||||
@@ -66,7 +64,6 @@ func ComputeDailyTotals(events []db.Event, minBreakThreshold int64, dayStart, da
|
|||||||
case StateWorking:
|
case StateWorking:
|
||||||
workTotal += e.OccurredAt - workStart
|
workTotal += e.OccurredAt - workStart
|
||||||
workStart = 0
|
workStart = 0
|
||||||
pairs++
|
|
||||||
breakStart = e.OccurredAt
|
breakStart = e.OccurredAt
|
||||||
state = StateOnBreak
|
state = StateOnBreak
|
||||||
case StateIdle:
|
case StateIdle:
|
||||||
@@ -81,6 +78,5 @@ func ComputeDailyTotals(events []db.Event, minBreakThreshold int64, dayStart, da
|
|||||||
return DailyTotals{
|
return DailyTotals{
|
||||||
TotalSeconds: workTotal,
|
TotalSeconds: workTotal,
|
||||||
BreakSeconds: breakTotal,
|
BreakSeconds: breakTotal,
|
||||||
PairCount: pairs,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user