diff options
Diffstat (limited to 'internal/handlers/tabs.go')
| -rw-r--r-- | internal/handlers/tabs.go | 384 |
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) - } -} |
