diff options
| author | Peter Stone <thepeterstone@gmail.com> | 2026-03-23 08:13:02 +0000 |
|---|---|---|
| committer | Peter Stone <thepeterstone@gmail.com> | 2026-03-23 08:13:02 +0000 |
| commit | b2d8fc460be3105ac383098e7cdc92171e5026ec (patch) | |
| tree | cd5ba3f3008e6b3310680d785880f1f32ed090c5 /internal/handlers/handlers.go | |
| parent | b0688c8819da1b7fcb4a97b6ec1fa58050e4841e (diff) | |
feat: unify Google Tasks with main system via caching and integrated UI
- Implement SQLite caching layer for Google Tasks
- Integrate Google Tasks into unified Atoms loop (showing in Tasks tab)
- Update Planning tab to include cached Google Tasks
- Enhance Quick Add form with Todoist project selector
- Remove orphaned HandleTasksTab/HandleRefreshTab methods
- Update tests to reflect new BuildTimeline signature and data structures
Diffstat (limited to 'internal/handlers/handlers.go')
| -rw-r--r-- | internal/handlers/handlers.go | 109 |
1 files changed, 87 insertions, 22 deletions
diff --git a/internal/handlers/handlers.go b/internal/handlers/handlers.go index b0fd952..8abd4d7 100644 --- a/internal/handlers/handlers.go +++ b/internal/handlers/handlers.go @@ -173,26 +173,6 @@ func (h *Handler) HandleGetShoppingList(w http.ResponseWriter, r *http.Request) JSONResponse(w, items) } -// HandleTasksTab renders the tasks tab content (Trello + Todoist + PlanToEat) -func (h *Handler) HandleTasksTab(w http.ResponseWriter, r *http.Request) { - data, err := h.aggregateData(r.Context(), false) - if err != nil { - JSONError(w, http.StatusInternalServerError, "Failed to load tasks", err) - return - } - HTMLResponse(w, h.renderer, "tasks-tab", data) -} - -// HandleRefreshTab refreshes and re-renders the specified tab -func (h *Handler) HandleRefreshTab(w http.ResponseWriter, r *http.Request) { - data, err := h.aggregateData(r.Context(), true) - if err != nil { - JSONError(w, http.StatusInternalServerError, "Failed to refresh", err) - return - } - HTMLResponse(w, h.renderer, "tasks-tab", data) -} - // aggregateData fetches and caches data from all sources concurrently func (h *Handler) aggregateData(ctx context.Context, forceRefresh bool) (*models.DashboardData, error) { data := &models.DashboardData{ @@ -267,6 +247,18 @@ func (h *Handler) aggregateData(ctx context.Context, forceRefresh bool) (*models }) } + if h.googleTasksClient != nil { + fetch("Google Tasks", func() error { + tasks, err := h.fetchGoogleTasks(ctx, forceRefresh) + if err == nil { + mu.Lock() + data.GoogleTasks = tasks + mu.Unlock() + } + return err + }) + } + wg.Wait() // Populate projects from cached tasks (avoids deprecated REST API) @@ -392,6 +384,22 @@ func (h *Handler) fetchCalendarEvents(ctx context.Context, forceRefresh bool) ([ return fetcher.FetchWithCache(ctx, forceRefresh) } +// fetchGoogleTasks fetches Google Tasks from cache or API +func (h *Handler) fetchGoogleTasks(ctx context.Context, forceRefresh bool) ([]models.GoogleTask, error) { + if h.googleTasksClient == nil { + return nil, nil + } + fetcher := &CacheFetcher[models.GoogleTask]{ + Store: h.store, + CacheKey: store.CacheKeyGoogleTasks, + TTLMinutes: h.config.CacheTTLMinutes, + Fetch: func(ctx context.Context) ([]models.GoogleTask, error) { return h.googleTasksClient.GetTasks(ctx) }, + GetFromCache: h.store.GetGoogleTasks, + SaveToCache: h.store.SaveGoogleTasks, + } + return fetcher.FetchWithCache(ctx, forceRefresh) +} + // fetchBoards fetches Trello boards from cache or API func (h *Handler) fetchBoards(ctx context.Context, forceRefresh bool) ([]models.Board, error) { fetcher := &CacheFetcher[models.Board]{ @@ -684,7 +692,8 @@ func (h *Handler) HandleUnifiedAdd(w http.ResponseWriter, r *http.Request) { switch source { case "todoist": - if _, err := h.todoistClient.CreateTask(ctx, title, "", dueDate, 1); err != nil { + projectID := r.FormValue("project_id") + if _, err := h.todoistClient.CreateTask(ctx, title, projectID, dueDate, 1); err != nil { JSONError(w, http.StatusInternalServerError, "Failed to create Todoist task", err) return } @@ -805,7 +814,7 @@ func (h *Handler) HandleUpdateTask(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) } -// HandleTabTasks renders the unified Tasks tab (Todoist + Trello cards with due dates + Bugs) +// HandleTabTasks renders the unified Tasks tab (Todoist + Trello cards with due dates + Bugs + Google Tasks) func (h *Handler) HandleTabTasks(w http.ResponseWriter, r *http.Request) { atoms, boards, err := BuildUnifiedAtomList(h.store) if err != nil { @@ -816,15 +825,19 @@ func (h *Handler) HandleTabTasks(w http.ResponseWriter, r *http.Request) { SortAtomsByUrgency(atoms) currentAtoms, futureAtoms := PartitionAtomsByTime(atoms) + projects, _ := h.store.GetProjectsFromTasks() + data := struct { Atoms []models.Atom FutureAtoms []models.Atom Boards []models.Board + Projects []models.Project Today string }{ Atoms: currentAtoms, FutureAtoms: futureAtoms, Boards: boards, + Projects: projects, Today: config.Now().Format("2006-01-02"), } @@ -839,6 +852,7 @@ func (h *Handler) HandleTabPlanning(w http.ResponseWriter, r *http.Request) { boards, _ := h.store.GetBoards() tasks, _ := h.store.GetTasks() + gTasks, _ := h.store.GetGoogleTasks() events, _ := h.fetchCalendarEvents(r.Context(), false) @@ -941,21 +955,72 @@ func (h *Handler) HandleTabPlanning(w http.ResponseWriter, r *http.Request) { } } + for _, gTask := range gTasks { + if gTask.Completed { + continue + } + + if gTask.DueDate == nil { + atom := models.GoogleTaskToAtom(gTask) + atom.ComputeUIFields() + unscheduled = append(unscheduled, atom) + continue + } + + dueDate := *gTask.DueDate + // Google Tasks usually don't have times, but if they did we'd handle them here + hasTime := dueDate.Hour() != 0 || dueDate.Minute() != 0 + + if dueDate.Before(tomorrow) { + if hasTime { + scheduled = append(scheduled, ScheduledItem{ + Type: "task", + ID: gTask.ID, + Title: gTask.Title, + Start: dueDate, + URL: gTask.URL, + Source: "gtasks", + SourceIcon: "🔵", + Priority: 2, + }) + } else { + atom := models.GoogleTaskToAtom(gTask) + atom.ComputeUIFields() + unscheduled = append(unscheduled, atom) + } + } else if dueDate.Before(in3Days) { + upcoming = append(upcoming, ScheduledItem{ + Type: "task", + ID: gTask.ID, + Title: gTask.Title, + Start: dueDate, + URL: gTask.URL, + Source: "gtasks", + SourceIcon: "🔵", + Priority: 2, + }) + } + } + sort.Slice(scheduled, func(i, j int) bool { return scheduled[i].Start.Before(scheduled[j].Start) }) sort.Slice(unscheduled, func(i, j int) bool { return unscheduled[i].Priority > unscheduled[j].Priority }) sort.Slice(upcoming, func(i, j int) bool { return upcoming[i].Start.Before(upcoming[j].Start) }) + projects, _ := h.store.GetProjectsFromTasks() + data := struct { Scheduled []ScheduledItem Unscheduled []models.Atom Upcoming []ScheduledItem Boards []models.Board + Projects []models.Project Today string }{ Scheduled: scheduled, Unscheduled: unscheduled, Upcoming: upcoming, Boards: boards, + Projects: projects, Today: today.Format("2006-01-02"), } |
