summaryrefslogtreecommitdiff
path: root/internal/handlers/tabs.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/handlers/tabs.go')
-rw-r--r--internal/handlers/tabs.go384
1 files changed, 0 insertions, 384 deletions
diff --git a/internal/handlers/tabs.go b/internal/handlers/tabs.go
deleted file mode 100644
index 87be344..0000000
--- a/internal/handlers/tabs.go
+++ /dev/null
@@ -1,384 +0,0 @@
-package handlers
-
-import (
- "html/template"
- "log"
- "net/http"
- "path/filepath"
- "sort"
- "strings"
- "time"
-
- "task-dashboard/internal/api"
- "task-dashboard/internal/models"
- "task-dashboard/internal/store"
-)
-
-// isActionableList returns true if the list name indicates an actionable list
-func isActionableList(name string) bool {
- lower := strings.ToLower(name)
- return strings.Contains(lower, "doing") ||
- strings.Contains(lower, "in progress") ||
- strings.Contains(lower, "to do") ||
- strings.Contains(lower, "todo") ||
- strings.Contains(lower, "tasks") ||
- strings.Contains(lower, "next") ||
- strings.Contains(lower, "today")
-}
-
-// atomUrgencyTier returns the urgency tier for sorting:
-// 0: Overdue, 1: Today with time, 2: Today all-day, 3: Future, 4: No due date
-func atomUrgencyTier(a models.Atom) int {
- if a.DueDate == nil {
- return 4 // No due date
- }
- if a.IsOverdue {
- return 0 // Overdue
- }
- if a.IsFuture {
- return 3 // Future
- }
- // Due today
- if a.HasSetTime {
- return 1 // Today with specific time
- }
- return 2 // Today all-day
-}
-
-// TabsHandler handles tab-specific rendering with Atom model
-type TabsHandler struct {
- store *store.Store
- googleCalendarClient api.GoogleCalendarAPI
- templates *template.Template
-}
-
-// NewTabsHandler creates a new TabsHandler instance
-func NewTabsHandler(store *store.Store, googleCalendarClient api.GoogleCalendarAPI, templateDir string) *TabsHandler {
- // Parse templates including partials
- tmpl, err := template.ParseGlob(filepath.Join(templateDir, "*.html"))
- if err != nil {
- log.Printf("Warning: failed to parse templates: %v", err)
- }
-
- // Also parse partials
- tmpl, err = tmpl.ParseGlob(filepath.Join(templateDir, "partials", "*.html"))
- if err != nil {
- log.Printf("Warning: failed to parse partial templates: %v", err)
- }
-
- return &TabsHandler{
- store: store,
- googleCalendarClient: googleCalendarClient,
- templates: tmpl,
- }
-}
-
-// HandleTasks renders the unified Tasks tab (Todoist + Trello cards with due dates)
-func (h *TabsHandler) HandleTasks(w http.ResponseWriter, r *http.Request) {
- // Fetch Todoist tasks
- tasks, err := h.store.GetTasks()
- if err != nil {
- http.Error(w, "Failed to fetch tasks", http.StatusInternalServerError)
- log.Printf("Error fetching tasks: %v", err)
- return
- }
-
- // 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
- }
-
- // Convert to Atoms
- atoms := make([]models.Atom, 0)
-
- // Convert Todoist tasks
- for _, task := range tasks {
- if !task.Completed {
- atoms = append(atoms, models.TaskToAtom(task))
- }
- }
-
- // Convert Trello cards with due dates or in actionable lists
- for _, board := range boards {
- for _, card := range board.Cards {
- if card.DueDate != nil || isActionableList(card.ListName) {
- atoms = append(atoms, models.CardToAtom(card))
- }
- }
- }
-
- // Compute UI fields (IsOverdue, IsFuture, HasSetTime)
- for i := range atoms {
- atoms[i].ComputeUIFields()
- }
-
- // Sort atoms by urgency tiers:
- // 1. Overdue (before today)
- // 2. Today with specific time
- // 3. Today all-day (midnight)
- // 4. Future
- // 5. No due date
- // Within each tier: sort by due date/time, then by priority
- sort.SliceStable(atoms, func(i, j int) bool {
- // Compute urgency tier (lower = more urgent)
- tierI := atomUrgencyTier(atoms[i])
- tierJ := atomUrgencyTier(atoms[j])
-
- if tierI != tierJ {
- return tierI < tierJ
- }
-
- // Same tier: sort by due date/time if both have dates
- if atoms[i].DueDate != nil && atoms[j].DueDate != nil {
- if !atoms[i].DueDate.Equal(*atoms[j].DueDate) {
- return atoms[i].DueDate.Before(*atoms[j].DueDate)
- }
- }
-
- // Same due date/time (or both nil), sort by priority (descending)
- return atoms[i].Priority > atoms[j].Priority
- })
-
- // Partition atoms into current (overdue + today) and future
- var currentAtoms, futureAtoms []models.Atom
- for _, a := range atoms {
- if a.IsFuture {
- futureAtoms = append(futureAtoms, a)
- } else {
- currentAtoms = append(currentAtoms, a)
- }
- }
-
- // Render template
- data := struct {
- Atoms []models.Atom // Current tasks (overdue + today)
- FutureAtoms []models.Atom // Future tasks (hidden by default)
- Boards []models.Board
- Today string
- }{
- Atoms: currentAtoms,
- FutureAtoms: futureAtoms,
- Boards: boards,
- Today: time.Now().Format("2006-01-02"),
- }
-
- if err := h.templates.ExecuteTemplate(w, "tasks-tab", data); err != nil {
- http.Error(w, "Failed to render template", http.StatusInternalServerError)
- log.Printf("Error rendering tasks tab: %v", err)
- }
-}
-
-// 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 {
- log.Printf("Error fetching boards: %v", err)
- 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 {
- events, err = h.googleCalendarClient.GetUpcomingEvents(r.Context(), 20)
- if err != nil {
- log.Printf("Error fetching calendar events: %v", err)
- }
- }
-
- // 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 {
- Scheduled []ScheduledItem
- Unscheduled []models.Atom
- Upcoming []ScheduledItem
- Boards []models.Board
- Today string
- }{
- 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 {
- http.Error(w, "Failed to render template", http.StatusInternalServerError)
- log.Printf("Error rendering planning tab: %v", err)
- }
-}
-
-// HandleMeals renders the Meals tab (PlanToEat)
-func (h *TabsHandler) HandleMeals(w http.ResponseWriter, r *http.Request) {
- // Fetch meals for next 7 days
- startDate := time.Now()
- endDate := startDate.AddDate(0, 0, 7)
-
- meals, err := h.store.GetMeals(startDate, endDate)
- if err != nil {
- http.Error(w, "Failed to fetch meals", http.StatusInternalServerError)
- log.Printf("Error fetching meals: %v", err)
- return
- }
-
- data := struct {
- Meals []models.Meal
- }{
- Meals: meals,
- }
-
- if err := h.templates.ExecuteTemplate(w, "meals-tab", data); err != nil {
- http.Error(w, "Failed to render template", http.StatusInternalServerError)
- log.Printf("Error rendering meals tab: %v", err)
- }
-}