summaryrefslogtreecommitdiff
path: root/internal/handlers/handlers.go
diff options
context:
space:
mode:
authorPeter Stone <thepeterstone@gmail.com>2026-01-21 22:53:37 -1000
committerPeter Stone <thepeterstone@gmail.com>2026-01-21 22:53:37 -1000
commit583f90c5dedf0235fa45557359b0e6e7dd62b0f0 (patch)
tree304e4527b6668669197fc9ffdf2ffc87566478f0 /internal/handlers/handlers.go
parentdd4689a71de8f1c0b5a2d483827411a9645ad66a (diff)
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 <noreply@anthropic.com>
Diffstat (limited to 'internal/handlers/handlers.go')
-rw-r--r--internal/handlers/handlers.go150
1 files changed, 145 insertions, 5 deletions
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(`<div class="task-item bg-gray-100 rounded-lg shadow-sm border-l-4 border-gray-300 opacity-60">
+ <div class="flex items-start gap-2 sm:gap-3 p-3 sm:p-4">
+ <input type="checkbox" checked disabled class="mt-1 h-5 w-5 rounded border-gray-300 text-green-600 cursor-not-allowed flex-shrink-0">
+ <span class="text-lg flex-shrink-0">✓</span>
+ <div class="flex-1 min-w-0">
+ <h3 class="text-sm font-medium text-gray-400 line-through break-words">%s</h3>
+ <div class="text-xs text-green-600 mt-1">Completed</div>
+ </div>
+ </div>
+ </div>`, 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(`
+ <div class="p-4">
+ <h3 class="font-semibold text-gray-900 mb-3">%s</h3>
+ <form hx-post="/tasks/update" hx-swap="none" hx-on::after-request="if(event.detail.successful) { closeTaskModal(); htmx.trigger(document.body, 'refresh-tasks'); }">
+ <input type="hidden" name="id" value="%s">
+ <input type="hidden" name="source" value="%s">
+ <label class="block text-sm font-medium text-gray-700 mb-1">Description</label>
+ <textarea name="description" class="w-full border border-gray-300 rounded-lg px-3 py-2 text-sm mb-3 h-32">%s</textarea>
+ <button type="submit" class="w-full bg-primary-600 hover:bg-primary-700 text-white px-4 py-2 rounded-lg text-sm font-medium">Save</button>
+ </form>
+ </div>
+ `, 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)
+}