refactor: break up large functions, add RPG/league/salary/burnout, fix bugs, add tests
- Refactor GenerateMonthlyReport (198→70), HandleCallback (128→85), handleHistoryCallback (131→38), handleExportCallback (100→25), checkAchievements (86→25) into extracted helper functions - Add RPG system (XP, levels, streak, achievements, burnout) - Add WorkTime League leaderboard - Add salary estimation with configurable currency/rate - Add input sanitization (SanitizeNote, SanitizeDisplayName) - Add settings select-style pickers for toggles (report, RPG, league) - Add break threshold inline picker UI - Add event notes with pending state and /note command - Add multi-calendar support (Jalali, Hijri) - Add Excel export with theme picker - Fix: getTodayBreakThreshold uses user's timezone (was UTC) - Fix: acknowledgeCallback passes real callback ID - Fix: currency symbol safety with currencySymbol() helper - Fix: count work hours in summary on day-off days - Fix: unsilence all error returns from SQL Exec/Bot API/migrations - Remove stale db/schema.sql and unreferenced computeWeekTotals - Extract constants: DateLayout, TimeLayout, secondsPerHour, etc. - Add 12 new tests (XP, salary, sanitize, currency, dateutil) - Remove duplicate package doc comment in totals.go
This commit is contained in:
@@ -32,7 +32,7 @@ func (h *Handler) buildStatusText(chatID int64) string {
|
||||
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")
|
||||
t := time.Unix(last.OccurredAt, 0).In(loc).Format(TimeLayout)
|
||||
text += fmt.Sprintf("\nStatus: working since %s", t)
|
||||
} else {
|
||||
text += "\nStatus: not working"
|
||||
@@ -49,14 +49,33 @@ func (h *Handler) buildStatusText(chatID int64) string {
|
||||
text += fmt.Sprintf("\nToday: %s worked", formatDuration(totals.TotalSeconds))
|
||||
}
|
||||
|
||||
loc := loadLocation(user.Timezone)
|
||||
weekTotal := h.computeWeekTotals(user, loc)
|
||||
if weekTotal > 0 {
|
||||
text += fmt.Sprintf("\nWeek: %s", formatDuration(weekTotal))
|
||||
// Salary info if configured
|
||||
st, _ := h.DB.GetOrCreateSettings(user.ID)
|
||||
if st.HourlyRate > 0 {
|
||||
if events, err := h.DB.EventsForDayByDayID(day.ID); 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)
|
||||
salLine := h.buildSalaryStatus(user.ID, totals.TotalSeconds)
|
||||
if salLine != "" {
|
||||
text += salLine
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if day.MinBreakThreshold > 0 {
|
||||
text += fmt.Sprintf("\nBreak threshold: %dm", day.MinBreakThreshold/60)
|
||||
// RPG info if enabled
|
||||
if st.RPGEnabled {
|
||||
stats, _ := h.DB.GetOrCreateRPGStats(user.ID)
|
||||
if stats != nil {
|
||||
text += fmt.Sprintf("\nLevel: %d | XP: %d", stats.Level, stats.XP)
|
||||
rankText := h.buildLeagueRankText(user.ID)
|
||||
if rankText != "" {
|
||||
text += fmt.Sprintf(" | %s", rankText)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
text += "\n\nChoose an action:"
|
||||
@@ -87,7 +106,7 @@ 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")
|
||||
todayStr := now.Format(DateLayout)
|
||||
dayEnd := time.Date(y, time.Month(m), d, 23, 59, 59, 0, loc).Unix()
|
||||
if day.Date == todayStr {
|
||||
dayEnd = now.Unix()
|
||||
@@ -101,7 +120,7 @@ func (h *Handler) buildDailyReport(user *db.User, day *db.Day, events []db.Event
|
||||
lines += "\n\n"
|
||||
|
||||
for _, e := range events {
|
||||
t := time.Unix(e.OccurredAt, 0).In(loc).Format("15:04")
|
||||
t := time.Unix(e.OccurredAt, 0).In(loc).Format(TimeLayout)
|
||||
label := "IN"
|
||||
if e.EventType == "out" {
|
||||
label = "OUT"
|
||||
@@ -122,6 +141,13 @@ func (h *Handler) buildDailyReport(user *db.User, day *db.Day, events []db.Event
|
||||
formatDuration(totals.TotalSeconds),
|
||||
formatDuration(totals.BreakSeconds))
|
||||
|
||||
// Salary info if configured
|
||||
st, _ := h.DB.GetOrCreateSettings(user.ID)
|
||||
if st.HourlyRate > 0 {
|
||||
est := computeDailySalary(totals.TotalSeconds, st.HourlyRate)
|
||||
lines += fmt.Sprintf(" Salary: %s\n", SalaryEstimate{GrossEarning: est}.formatSalary(st.Currency))
|
||||
}
|
||||
|
||||
return lines
|
||||
}
|
||||
|
||||
@@ -137,7 +163,7 @@ func (h *Handler) SendDailyReport(ctx context.Context, userID int64, chatID int6
|
||||
loc = time.UTC
|
||||
}
|
||||
now := time.Now().In(loc)
|
||||
today := now.Format("2006-01-02")
|
||||
today := now.Format(DateLayout)
|
||||
|
||||
day, err := h.DB.GetOrCreateDay(user.ID, today)
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user