From 583f90c5dedf0235fa45557359b0e6e7dd62b0f0 Mon Sep 17 00:00:00 2001 From: Peter Stone Date: Wed, 21 Jan 2026 22:53:37 -1000 Subject: Implement 10 UI/UX improvements and bug fixes - Fix outdated Todoist task URL format (showTask -> app/task) - Fix quick-add date defaulting to tomorrow in evening (client-side JS) - Add tap-to-expand for task descriptions with checkbox completion - Add visual differentiation: overdue (red), future (gray), today (normal) - Sort tasks by urgency: overdue > today-timed > today-allday > future - Keep completed tasks visible with strikethrough until refresh - Add random Unsplash landscape background with content overlay - Hide future tasks behind collapsible fold with count badge - Unified modal menu for Quick Add + Bug Report (Ctrl+K shortcut) - Click task title to edit description in modal Co-Authored-By: Claude Opus 4.5 --- internal/handlers/handlers.go | 150 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 145 insertions(+), 5 deletions(-) (limited to 'internal/handlers/handlers.go') diff --git a/internal/handlers/handlers.go b/internal/handlers/handlers.go index e4d6457..73a05f0 100644 --- a/internal/handlers/handlers.go +++ b/internal/handlers/handlers.go @@ -78,15 +78,21 @@ func (h *Handler) HandleDashboard(w http.ResponseWriter, r *http.Request) { return } + // Generate random background URL (Unsplash Source API) + // Add timestamp to prevent caching + backgroundURL := fmt.Sprintf("https://source.unsplash.com/1920x1080/?landscape,nature&t=%d", time.Now().UnixNano()) + // Wrap dashboard data with active tab for template data := struct { *models.DashboardData - ActiveTab string - CSRFToken string + ActiveTab string + CSRFToken string + BackgroundURL string }{ DashboardData: dashboardData, ActiveTab: tab, CSRFToken: auth.GetCSRFTokenFromContext(ctx), + BackgroundURL: backgroundURL, } if err := h.templates.ExecuteTemplate(w, "index.html", data); err != nil { @@ -411,7 +417,7 @@ func (h *Handler) convertSyncItemToTask(item api.SyncItemResponse, projectMap ma Priority: item.Priority, Completed: false, Labels: item.Labels, - URL: fmt.Sprintf("https://todoist.com/showTask?id=%s", item.ID), + URL: fmt.Sprintf("https://todoist.com/app/task/%s", item.ID), } if item.AddedAt != "" { @@ -728,6 +734,34 @@ func (h *Handler) HandleCompleteAtom(w http.ResponseWriter, r *http.Request) { return } + // Get task title before removing from cache + var title string + switch source { + case "todoist": + if tasks, err := h.store.GetTasks(); err == nil { + for _, t := range tasks { + if t.ID == id { + title = t.Content + break + } + } + } + case "trello": + if boards, err := h.store.GetBoards(); err == nil { + for _, b := range boards { + for _, c := range b.Cards { + if c.ID == id { + title = c.Name + break + } + } + } + } + } + if title == "" { + title = "Task" + } + // Remove from local cache switch source { case "todoist": @@ -740,8 +774,19 @@ func (h *Handler) HandleCompleteAtom(w http.ResponseWriter, r *http.Request) { } } - // Return 200 OK with empty body to remove the element from DOM - w.WriteHeader(http.StatusOK) + // Return completed task HTML (stays visible with strikethrough until refresh) + w.Header().Set("Content-Type", "text/html") + completedHTML := fmt.Sprintf(`
+
+ + +
+

%s

+
Completed
+
+
+
`, template.HTMLEscapeString(title)) + w.Write([]byte(completedHTML)) } // HandleUnifiedAdd creates a task in Todoist or a card in Trello from the Quick Add form @@ -875,3 +920,98 @@ func (h *Handler) HandleReportBug(w http.ResponseWriter, r *http.Request) { // Return updated bug list h.HandleGetBugs(w, r) } + +// HandleGetTaskDetail returns task details as HTML for modal +func (h *Handler) HandleGetTaskDetail(w http.ResponseWriter, r *http.Request) { + id := r.URL.Query().Get("id") + source := r.URL.Query().Get("source") + + if id == "" || source == "" { + http.Error(w, "Missing id or source", http.StatusBadRequest) + return + } + + var title, description string + switch source { + case "todoist": + tasks, err := h.store.GetTasks() + if err == nil { + for _, t := range tasks { + if t.ID == id { + title = t.Content + description = t.Description + break + } + } + } + case "trello": + boards, err := h.store.GetBoards() + if err == nil { + for _, b := range boards { + for _, c := range b.Cards { + if c.ID == id { + title = c.Name + // Card model doesn't store description, leave empty + description = "" + break + } + } + } + } + } + + w.Header().Set("Content-Type", "text/html") + html := fmt.Sprintf(` +
+

%s

+
+ + + + + +
+
+ `, template.HTMLEscapeString(title), template.HTMLEscapeString(id), template.HTMLEscapeString(source), template.HTMLEscapeString(description)) + w.Write([]byte(html)) +} + +// HandleUpdateTask updates a task description +func (h *Handler) HandleUpdateTask(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + if err := r.ParseForm(); err != nil { + http.Error(w, "Failed to parse form", http.StatusBadRequest) + return + } + + id := r.FormValue("id") + source := r.FormValue("source") + description := r.FormValue("description") + + if id == "" || source == "" { + http.Error(w, "Missing id or source", http.StatusBadRequest) + return + } + + var err error + switch source { + case "todoist": + updates := map[string]interface{}{"description": description} + err = h.todoistClient.UpdateTask(ctx, id, updates) + case "trello": + updates := map[string]interface{}{"desc": description} + err = h.trelloClient.UpdateCard(ctx, id, updates) + default: + http.Error(w, "Unknown source", http.StatusBadRequest) + return + } + + if err != nil { + http.Error(w, "Failed to update task", http.StatusInternalServerError) + log.Printf("Error updating task: %v", err) + return + } + + w.WriteHeader(http.StatusOK) +} -- cgit v1.2.3