package handlers
import (
"html/template"
"log"
"net/http"
"sort"
"time"
"task-dashboard/internal/models"
"task-dashboard/internal/store"
)
// TabsHandler handles tab-specific rendering with Atom model
type TabsHandler struct {
store *store.Store
templates *template.Template
}
// NewTabsHandler creates a new TabsHandler instance
func NewTabsHandler(store *store.Store) *TabsHandler {
// Parse templates including partials
tmpl, err := template.ParseGlob("web/templates/*.html")
if err != nil {
log.Printf("Warning: failed to parse templates: %v", err)
}
// Also parse partials
tmpl, err = tmpl.ParseGlob("web/templates/partials/*.html")
if err != nil {
log.Printf("Warning: failed to parse partial templates: %v", err)
}
return &TabsHandler{
store: store,
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
for _, board := range boards {
for _, card := range board.Cards {
if card.DueDate != nil {
atoms = append(atoms, models.CardToAtom(card))
}
}
}
// Sort atoms: by DueDate (earliest first), then by Priority (descending)
sort.SliceStable(atoms, func(i, j int) bool {
// Handle nil due dates (push to end)
if atoms[i].DueDate == nil && atoms[j].DueDate != nil {
return false
}
if atoms[i].DueDate != nil && atoms[j].DueDate == nil {
return true
}
// Both have due dates, sort by date
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 (or both nil), sort by priority (descending)
return atoms[i].Priority > atoms[j].Priority
})
// Render template
data := struct {
Atoms []models.Atom
}{
Atoms: atoms,
}
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)
}
}
// HandlePlanning renders the Planning tab (Trello boards)
func (h *TabsHandler) HandlePlanning(w http.ResponseWriter, r *http.Request) {
// 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
}
data := struct {
Boards []models.Board
}{
Boards: boards,
}
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)
}
}
// HandleNotes renders the Notes tab (Obsidian notes)
func (h *TabsHandler) HandleNotes(w http.ResponseWriter, r *http.Request) {
// Check for search query parameter
query := r.URL.Query().Get("q")
var notes []models.Note
var err error
// If search query is present, search notes; otherwise, get all notes
if query != "" {
notes, err = h.store.SearchNotes(query)
} else {
notes, err = h.store.GetNotes(20)
}
if err != nil {
http.Error(w, "Failed to fetch notes", http.StatusInternalServerError)
log.Printf("Error fetching notes: %v", err)
return
}
data := struct {
Notes []models.Note
}{
Notes: notes,
}
// Check HX-Target header for partial update
hxTarget := r.Header.Get("HX-Target")
templateName := "notes-tab"
if hxTarget == "notes-results" {
// Render only the notes list for HTMX partial update
templateName = "obsidian-notes"
}
if err := h.templates.ExecuteTemplate(w, templateName, data); err != nil {
http.Error(w, "Failed to render template", http.StatusInternalServerError)
log.Printf("Error rendering notes 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)
}
}