From dd4689a71de8f1c0b5a2d483827411a9645ad66a Mon Sep 17 00:00:00 2001 From: Peter Stone Date: Tue, 20 Jan 2026 21:38:18 -1000 Subject: UI improvements and bug fixes - Gray out overdue tasks (past today) - Sort tasks with specific times before midnight-due tasks - Fix timezone bug in quick add (use local timezone) - Remove "Personal Dashboard" header, minimal refresh/logout bar - Change Todoist icon from checkmark to red circle - Show due time when set (not just date) - Compact task list styling Co-Authored-By: Claude Opus 4.5 --- internal/handlers/handlers.go | 4 +- internal/handlers/tabs.go | 31 +++++++++++--- internal/models/atom.go | 21 ++++++++- web/templates/index.html | 42 ++++++------------ web/templates/partials/tasks-tab.html | 81 ++++++++++++++++------------------- 5 files changed, 97 insertions(+), 82 deletions(-) diff --git a/internal/handlers/handlers.go b/internal/handlers/handlers.go index 8d809ae..e4d6457 100644 --- a/internal/handlers/handlers.go +++ b/internal/handlers/handlers.go @@ -762,10 +762,10 @@ func (h *Handler) HandleUnifiedAdd(w http.ResponseWriter, r *http.Request) { return } - // Parse due date if provided + // Parse due date if provided (use local timezone) var dueDate *time.Time if dueDateStr != "" { - parsed, err := time.Parse("2006-01-02", dueDateStr) + parsed, err := time.ParseInLocation("2006-01-02", dueDateStr, time.Local) if err == nil { dueDate = &parsed } diff --git a/internal/handlers/tabs.go b/internal/handlers/tabs.go index 7e0b352..bd15710 100644 --- a/internal/handlers/tabs.go +++ b/internal/handlers/tabs.go @@ -88,7 +88,12 @@ func (h *TabsHandler) HandleTasks(w http.ResponseWriter, r *http.Request) { } } - // Sort atoms: by DueDate (earliest first), then by Priority (descending) + // Compute UI fields (IsOverdue, HasSetTime) + for i := range atoms { + atoms[i].ComputeUIFields() + } + + // Sort atoms: by DueDate (earliest first), then by HasSetTime, then by Priority sort.SliceStable(atoms, func(i, j int) bool { // Handle nil due dates (push to end) if atoms[i].DueDate == nil && atoms[j].DueDate != nil { @@ -98,14 +103,30 @@ func (h *TabsHandler) HandleTasks(w http.ResponseWriter, r *http.Request) { return true } - // Both have due dates, sort by date + // Both have due dates if atoms[i].DueDate != nil && atoms[j].DueDate != nil { - if !atoms[i].DueDate.Equal(*atoms[j].DueDate) { - return atoms[i].DueDate.Before(*atoms[j].DueDate) + // Compare by date only (ignore time) + dateI := atoms[i].DueDate.Truncate(24 * time.Hour) + dateJ := atoms[j].DueDate.Truncate(24 * time.Hour) + + if !dateI.Equal(dateJ) { + return dateI.Before(dateJ) + } + + // Same day: tasks with set times come before midnight tasks + if atoms[i].HasSetTime != atoms[j].HasSetTime { + return atoms[i].HasSetTime + } + + // Both have set times or both are midnight, sort by actual time + if atoms[i].HasSetTime && atoms[j].HasSetTime { + if !atoms[i].DueDate.Equal(*atoms[j].DueDate) { + return atoms[i].DueDate.Before(*atoms[j].DueDate) + } } } - // Same due date (or both nil), sort by priority (descending) + // Same due date/time (or both nil), sort by priority (descending) return atoms[i].Priority > atoms[j].Priority }) diff --git a/internal/models/atom.go b/internal/models/atom.go index fe40962..b3a384a 100644 --- a/internal/models/atom.go +++ b/internal/models/atom.go @@ -36,11 +36,30 @@ type Atom struct { // UI Helpers (to be populated by mappers) SourceIcon string // e.g., "trello-icon.svg" or emoji ColorClass string // e.g., "border-blue-500" + IsOverdue bool // True if due date is before today + HasSetTime bool // True if due time is not midnight (has specific time) // Original Data (for write operations) Raw interface{} } +// ComputeUIFields calculates IsOverdue and HasSetTime based on DueDate +func (a *Atom) ComputeUIFields() { + if a.DueDate == nil { + return + } + + now := time.Now() + today := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location()) + + // Check if overdue (due date is before today) + dueDay := time.Date(a.DueDate.Year(), a.DueDate.Month(), a.DueDate.Day(), 0, 0, 0, 0, a.DueDate.Location()) + a.IsOverdue = dueDay.Before(today) + + // Check if has set time (not midnight) + a.HasSetTime = a.DueDate.Hour() != 0 || a.DueDate.Minute() != 0 +} + // TaskToAtom converts a Todoist Task to an Atom func TaskToAtom(t Task) Atom { // Todoist priority: 1 (normal) to 4 (urgent) @@ -63,7 +82,7 @@ func TaskToAtom(t Task) Atom { DueDate: t.DueDate, CreatedAt: t.CreatedAt, Priority: priority, - SourceIcon: "✓", // Checkmark emoji for tasks + SourceIcon: "🔴", // Red circle for Todoist ColorClass: "border-red-500", Raw: t, } diff --git a/web/templates/index.html b/web/templates/index.html index 2cd4c59..18aa56b 100644 --- a/web/templates/index.html +++ b/web/templates/index.html @@ -8,43 +8,25 @@ -
- - - - -
+
+ +
- {{.LastUpdated.Format "3:04 PM"}} -
+ {{.LastUpdated.Format "3:04 PM"}} + - +
-
+
+ {{else}} +
+

No tasks found.

+
+ {{end}}
{{end}} -- cgit v1.2.3