feat: Hijri calendar, virtual midnight crossover, overlap rejection, WAL mode

- Hijri (قمری) calendar added as 3rd calendar option with tabular conversion
- Calendar type select menu (like accent) with gregorian/jalali/hijri
- Calendar type applied to exports and reports
- Midnight crossover: virtual splits at computation time (no synthetic DB events)
  - Handles both UTC and local timezone boundaries
  - Timezone changes handled naturally (recomputed on-the-fly)
- Rate limiting per user ID instead of chat ID
- WAL mode enabled for SQLite
- Smart reply keyboard updates after every state change
- Overlapping time edits rejected (must preserve in/out alternation)
- Single event deletion warns if timeline needs repair
- Day off no longer prevents clock in/out
- Accent colors shown with descriptive names in select menu
- Delete button label changed to DEL for visual distinction
This commit is contained in:
2026-06-24 09:39:15 +03:30
parent 6f6cc6f184
commit 9d123316e6
6 changed files with 1030 additions and 59 deletions

View File

@@ -20,7 +20,7 @@ type DailyTotals struct {
PairCount int
}
func ComputeDailyTotals(events []db.Event, minBreakThreshold int64) DailyTotals {
func ComputeDailyTotals(events []db.Event, minBreakThreshold int64, dayStart, dayEnd int64) DailyTotals {
if len(events) == 0 {
return DailyTotals{}
}
@@ -30,6 +30,15 @@ func ComputeDailyTotals(events []db.Event, minBreakThreshold int64) DailyTotals
return sorted[i].OccurredAt < sorted[j].OccurredAt
})
if dayStart > 0 && len(sorted) > 0 && sorted[0].EventType == "out" {
first := db.Event{EventType: "in", OccurredAt: dayStart}
sorted = append([]db.Event{first}, sorted...)
}
if dayEnd > 0 && len(sorted) > 0 && sorted[len(sorted)-1].EventType == "in" {
last := db.Event{EventType: "out", OccurredAt: dayEnd}
sorted = append(sorted, last)
}
var workTotal, breakTotal int64
var workStart, breakStart int64
pairs := 0