From a7a9aa3dcfe4b90d9b32791c8313a0019ad11289 Mon Sep 17 00:00:00 2001 From: Peter Stone Date: Tue, 13 Jan 2026 14:20:41 -1000 Subject: Implement Todoist write operations - Handlers & UI (Part 2) Complete Todoist task creation and completion functionality: Handlers: - Update aggregateData to fetch and populate Projects - Add HandleCreateTask: creates task, refreshes list, re-renders - Add HandleCompleteTask: marks task complete, returns empty - Both handlers pass Projects to template for dropdown Routes: - Register POST /tasks for task creation - Register POST /tasks/complete for task completion UI (todoist-tasks.html): - Add Quick Add form with collapsible details element - Project selector dropdown (iterates over .Projects) - Content input field with validation - HTMX integration: hx-post, hx-target, hx-swap - Functional completion checkboxes on each task - Remove disabled attribute from checkboxes - Add todoist-task-item wrapper class for HTMX targeting - Glassmorphism styling for form Features: - Create Todoist tasks with optional project assignment - Mark tasks complete with single click (disappears) - Real-time task list updates without page reload - Seamless HTMX partial updates All tests pass. Full Todoist write operations now live in UI! Co-Authored-By: Claude Sonnet 4.5 --- internal/handlers/handlers.go | 101 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) (limited to 'internal') diff --git a/internal/handlers/handlers.go b/internal/handlers/handlers.go index 8762035..c3e49ed 100644 --- a/internal/handlers/handlers.go +++ b/internal/handlers/handlers.go @@ -254,6 +254,20 @@ func (h *Handler) aggregateData(ctx context.Context, forceRefresh bool) (*models } }() + // Fetch Todoist projects + wg.Add(1) + go func() { + defer wg.Done() + projects, err := h.todoistClient.GetProjects(ctx) + mu.Lock() + defer mu.Unlock() + if err != nil { + log.Printf("Failed to fetch projects: %v", err) + } else { + data.Projects = projects + } + }() + // Fetch Obsidian notes (if configured) if h.obsidianClient != nil { wg.Add(1) @@ -528,3 +542,90 @@ func (h *Handler) HandleCompleteCard(w http.ResponseWriter, r *http.Request) { // Return empty response (card will be removed from DOM) w.WriteHeader(http.StatusOK) } + +// HandleCreateTask creates a new Todoist task +func (h *Handler) HandleCreateTask(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + // Parse form data + if err := r.ParseForm(); err != nil { + http.Error(w, "Failed to parse form", http.StatusBadRequest) + log.Printf("Error parsing form: %v", err) + return + } + + content := r.FormValue("content") + projectID := r.FormValue("project_id") + + if content == "" { + http.Error(w, "Missing content", http.StatusBadRequest) + return + } + + // Create the task + _, err := h.todoistClient.CreateTask(ctx, content, projectID, nil, 0) + if err != nil { + http.Error(w, "Failed to create task", http.StatusInternalServerError) + log.Printf("Error creating task: %v", err) + return + } + + // Force refresh to get updated tasks + tasks, err := h.fetchTasks(ctx, true) + if err != nil { + http.Error(w, "Failed to refresh tasks", http.StatusInternalServerError) + log.Printf("Error refreshing tasks: %v", err) + return + } + + // Fetch projects for the dropdown + projects, err := h.todoistClient.GetProjects(ctx) + if err != nil { + log.Printf("Failed to fetch projects: %v", err) + projects = []models.Project{} + } + + // Prepare data for template rendering + data := struct { + Tasks []models.Task + Projects []models.Project + }{ + Tasks: tasks, + Projects: projects, + } + + // Render the updated task list + if err := h.templates.ExecuteTemplate(w, "todoist-tasks", data); err != nil { + http.Error(w, "Failed to render template", http.StatusInternalServerError) + log.Printf("Error rendering todoist tasks template: %v", err) + } +} + +// HandleCompleteTask marks a Todoist task as complete +func (h *Handler) HandleCompleteTask(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + // Parse form data + if err := r.ParseForm(); err != nil { + http.Error(w, "Failed to parse form", http.StatusBadRequest) + log.Printf("Error parsing form: %v", err) + return + } + + taskID := r.FormValue("task_id") + + if taskID == "" { + http.Error(w, "Missing task_id", http.StatusBadRequest) + return + } + + // Mark task as complete + if err := h.todoistClient.CompleteTask(ctx, taskID); err != nil { + http.Error(w, "Failed to complete task", http.StatusInternalServerError) + log.Printf("Error completing task: %v", err) + return + } + + // Return empty response (task will be removed from DOM) + w.WriteHeader(http.StatusOK) +} -- cgit v1.2.3