diff options
| author | Peter Stone <thepeterstone@gmail.com> | 2026-01-23 16:10:52 -1000 |
|---|---|---|
| committer | Peter Stone <thepeterstone@gmail.com> | 2026-01-23 16:10:52 -1000 |
| commit | 7828e19501b3ca8b2e86ca7297f580c659e5c9b8 (patch) | |
| tree | e3113af67fcecc459d81b213d7f043630dccae98 /internal/handlers/tabs.go | |
| parent | d11334c0999efb670a8eab93527a50f644fdfceb (diff) | |
Fix bugs #24-27: calendar dedup, uncomplete tasks, planning view
Bug fixes:
- #24: Deduplicate calendar events across multiple calendars using
summary + start time as key
- #25: Change event icon from calendar to clock to avoid confusion
with date display
- #26: Add task uncomplete functionality via ReopenTask API for
Todoist and closed=false for Trello
- #27: Restructure planning view with sections for Scheduled (timed
events/tasks), Today (unscheduled), Quick Add, and Upcoming (3 days)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Diffstat (limited to 'internal/handlers/tabs.go')
| -rw-r--r-- | internal/handlers/tabs.go | 178 |
1 files changed, 162 insertions, 16 deletions
diff --git a/internal/handlers/tabs.go b/internal/handlers/tabs.go index 986222a..87be344 100644 --- a/internal/handlers/tabs.go +++ b/internal/handlers/tabs.go @@ -171,39 +171,185 @@ func (h *TabsHandler) HandleTasks(w http.ResponseWriter, r *http.Request) { } } -// HandlePlanning renders the Planning tab (Trello boards) +// ScheduledItem represents a scheduled event or task for the planning view +type ScheduledItem struct { + Type string // "event" or "task" + ID string + Title string + Description string + Start time.Time + End time.Time + URL string + Source string // "todoist", "trello", "calendar" + SourceIcon string + Priority int +} + +// HandlePlanning renders the Planning tab with structured sections func (h *TabsHandler) HandlePlanning(w http.ResponseWriter, r *http.Request) { + now := time.Now() + today := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location()) + tomorrow := today.AddDate(0, 0, 1) + in3Days := today.AddDate(0, 0, 4) // End of 3rd day + // Fetch Trello boards boards, err := h.store.GetBoards() if err != nil { - http.Error(w, "Failed to fetch boards", http.StatusInternalServerError) log.Printf("Error fetching boards: %v", err) - return + boards = []models.Board{} + } + + // Fetch Todoist tasks + tasks, err := h.store.GetTasks() + if err != nil { + log.Printf("Error fetching tasks: %v", err) + tasks = []models.Task{} } // Fetch Google Calendar events var events []models.CalendarEvent if h.googleCalendarClient != nil { - var err error - events, err = h.googleCalendarClient.GetUpcomingEvents(r.Context(), 10) + events, err = h.googleCalendarClient.GetUpcomingEvents(r.Context(), 20) if err != nil { log.Printf("Error fetching calendar events: %v", err) - // Don't fail the whole request, just show empty events - } else { - log.Printf("Fetched %d calendar events", len(events)) } - } else { - log.Printf("Google Calendar client not configured") } + // Categorize into sections + var scheduled []ScheduledItem // Events and timed tasks for today + var unscheduled []models.Atom // Tasks due today without specific time + var upcoming []ScheduledItem // Events and tasks for next 3 days + + // Process calendar events + for _, event := range events { + item := ScheduledItem{ + Type: "event", + ID: event.ID, + Title: event.Summary, + Description: event.Description, + Start: event.Start, + End: event.End, + URL: event.HTMLLink, + Source: "calendar", + SourceIcon: "📅", + } + + if event.Start.Before(tomorrow) { + scheduled = append(scheduled, item) + } else if event.Start.Before(in3Days) { + upcoming = append(upcoming, item) + } + } + + // Process Todoist tasks + for _, task := range tasks { + if task.Completed || task.DueDate == nil { + continue + } + dueDate := *task.DueDate + + // Check if task has a specific time (not midnight) + hasTime := dueDate.Hour() != 0 || dueDate.Minute() != 0 + + if dueDate.Before(tomorrow) { + if hasTime { + // Timed task for today -> scheduled + scheduled = append(scheduled, ScheduledItem{ + Type: "task", + ID: task.ID, + Title: task.Content, + Start: dueDate, + URL: task.URL, + Source: "todoist", + SourceIcon: "✓", + Priority: task.Priority, + }) + } else { + // All-day task for today -> unscheduled + atom := models.TaskToAtom(task) + atom.ComputeUIFields() + unscheduled = append(unscheduled, atom) + } + } else if dueDate.Before(in3Days) { + upcoming = append(upcoming, ScheduledItem{ + Type: "task", + ID: task.ID, + Title: task.Content, + Start: dueDate, + URL: task.URL, + Source: "todoist", + SourceIcon: "✓", + Priority: task.Priority, + }) + } + } + + // Process Trello cards with due dates + for _, board := range boards { + for _, card := range board.Cards { + if card.DueDate == nil { + continue + } + dueDate := *card.DueDate + hasTime := dueDate.Hour() != 0 || dueDate.Minute() != 0 + + if dueDate.Before(tomorrow) { + if hasTime { + scheduled = append(scheduled, ScheduledItem{ + Type: "task", + ID: card.ID, + Title: card.Name, + Start: dueDate, + URL: card.URL, + Source: "trello", + SourceIcon: "📋", + }) + } else { + atom := models.CardToAtom(card) + atom.ComputeUIFields() + unscheduled = append(unscheduled, atom) + } + } else if dueDate.Before(in3Days) { + upcoming = append(upcoming, ScheduledItem{ + Type: "task", + ID: card.ID, + Title: card.Name, + Start: dueDate, + URL: card.URL, + Source: "trello", + SourceIcon: "📋", + }) + } + } + } + + // Sort scheduled by start time + sort.Slice(scheduled, func(i, j int) bool { + return scheduled[i].Start.Before(scheduled[j].Start) + }) + + // Sort unscheduled by priority (higher first) + sort.Slice(unscheduled, func(i, j int) bool { + return unscheduled[i].Priority > unscheduled[j].Priority + }) + + // Sort upcoming by date + sort.Slice(upcoming, func(i, j int) bool { + return upcoming[i].Start.Before(upcoming[j].Start) + }) + data := struct { - Boards []models.Board - Projects []models.Project - Events []models.CalendarEvent + Scheduled []ScheduledItem + Unscheduled []models.Atom + Upcoming []ScheduledItem + Boards []models.Board + Today string }{ - Boards: boards, - Projects: []models.Project{}, // Empty for now - Events: events, + Scheduled: scheduled, + Unscheduled: unscheduled, + Upcoming: upcoming, + Boards: boards, + Today: today.Format("2006-01-02"), } if err := h.templates.ExecuteTemplate(w, "planning-tab", data); err != nil { |
