diff options
Diffstat (limited to 'internal/handlers')
| -rw-r--r-- | internal/handlers/handlers.go | 114 | ||||
| -rw-r--r-- | internal/handlers/handlers_test.go | 35 | ||||
| -rw-r--r-- | internal/handlers/tab_state_test.go | 9 | ||||
| -rw-r--r-- | internal/handlers/tabs.go | 56 | ||||
| -rw-r--r-- | internal/handlers/template_test.go | 40 |
5 files changed, 25 insertions, 229 deletions
diff --git a/internal/handlers/handlers.go b/internal/handlers/handlers.go index f53eced..7bb84b9 100644 --- a/internal/handlers/handlers.go +++ b/internal/handlers/handlers.go @@ -24,14 +24,13 @@ type Handler struct { store *store.Store todoistClient api.TodoistAPI trelloClient api.TrelloAPI - obsidianClient api.ObsidianAPI planToEatClient api.PlanToEatAPI config *config.Config templates *template.Template } // New creates a new Handler instance -func New(s *store.Store, todoist api.TodoistAPI, trello api.TrelloAPI, obsidian api.ObsidianAPI, planToEat api.PlanToEatAPI, cfg *config.Config) *Handler { +func New(s *store.Store, todoist api.TodoistAPI, trello api.TrelloAPI, planToEat api.PlanToEatAPI, cfg *config.Config) *Handler { // Parse templates including partials tmpl, err := template.ParseGlob(filepath.Join(cfg.TemplateDir, "*.html")) if err != nil { @@ -48,7 +47,6 @@ func New(s *store.Store, todoist api.TodoistAPI, trello api.TrelloAPI, obsidian store: s, todoistClient: todoist, trelloClient: trello, - obsidianClient: obsidian, planToEatClient: planToEat, config: cfg, templates: tmpl, @@ -122,18 +120,6 @@ func (h *Handler) HandleGetTasks(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(tasks) } -// HandleGetNotes returns notes as JSON -func (h *Handler) HandleGetNotes(w http.ResponseWriter, r *http.Request) { - notes, err := h.store.GetNotes(20) - if err != nil { - http.Error(w, "Failed to get notes", http.StatusInternalServerError) - return - } - - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(notes) -} - // HandleGetMeals returns meals as JSON func (h *Handler) HandleGetMeals(w http.ResponseWriter, r *http.Request) { startDate := time.Now() @@ -178,27 +164,9 @@ func (h *Handler) HandleTasksTab(w http.ResponseWriter, r *http.Request) { } } -// HandleNotesTab renders the notes tab content (Obsidian) -func (h *Handler) HandleNotesTab(w http.ResponseWriter, r *http.Request) { - ctx := r.Context() - - data, err := h.aggregateData(ctx, false) - if err != nil { - http.Error(w, "Failed to load notes", http.StatusInternalServerError) - log.Printf("Error loading notes tab: %v", err) - return - } - - if err := h.templates.ExecuteTemplate(w, "notes-tab", data); err != nil { - http.Error(w, "Failed to render template", http.StatusInternalServerError) - log.Printf("Error rendering notes tab: %v", err) - } -} - // HandleRefreshTab refreshes and re-renders the specified tab func (h *Handler) HandleRefreshTab(w http.ResponseWriter, r *http.Request) { ctx := r.Context() - tab := r.URL.Query().Get("tab") // "tasks" or "notes" // Force refresh data, err := h.aggregateData(ctx, true) @@ -208,13 +176,7 @@ func (h *Handler) HandleRefreshTab(w http.ResponseWriter, r *http.Request) { return } - // Determine template to render - templateName := "tasks-tab" - if tab == "notes" { - templateName = "notes-tab" - } - - if err := h.templates.ExecuteTemplate(w, templateName, data); err != nil { + if err := h.templates.ExecuteTemplate(w, "tasks-tab", data); err != nil { http.Error(w, "Failed to render template", http.StatusInternalServerError) log.Printf("Error rendering refreshed tab: %v", err) } @@ -254,6 +216,26 @@ func (h *Handler) aggregateData(ctx context.Context, forceRefresh bool) (*models if err != nil { data.Errors = append(data.Errors, "Todoist: "+err.Error()) } else { + // Sort tasks: earliest due date first, nil last, then by priority (descending) + sort.Slice(tasks, func(i, j int) bool { + // Handle nil due dates (push to end) + if tasks[i].DueDate == nil && tasks[j].DueDate != nil { + return false + } + if tasks[i].DueDate != nil && tasks[j].DueDate == nil { + return true + } + + // Both have due dates, sort by date + if tasks[i].DueDate != nil && tasks[j].DueDate != nil { + if !tasks[i].DueDate.Equal(*tasks[j].DueDate) { + return tasks[i].DueDate.Before(*tasks[j].DueDate) + } + } + + // Same due date (or both nil), sort by priority (descending) + return tasks[i].Priority > tasks[j].Priority + }) data.Tasks = tasks } }() @@ -272,22 +254,6 @@ func (h *Handler) aggregateData(ctx context.Context, forceRefresh bool) (*models } }() - // Fetch Obsidian notes (if configured) - if h.obsidianClient != nil { - wg.Add(1) - go func() { - defer wg.Done() - notes, err := h.fetchNotes(ctx, forceRefresh) - mu.Lock() - defer mu.Unlock() - if err != nil { - data.Errors = append(data.Errors, "Obsidian: "+err.Error()) - } else { - data.Notes = notes - } - }() - } - // Fetch PlanToEat meals (if configured) if h.planToEatClient != nil { wg.Add(1) @@ -467,42 +433,6 @@ func (h *Handler) convertSyncItemToTask(item api.SyncItemResponse, projectMap ma return task } -// fetchNotes fetches notes from cache or filesystem -func (h *Handler) fetchNotes(ctx context.Context, forceRefresh bool) ([]models.Note, error) { - cacheKey := store.CacheKeyObsidianNotes - - // Check cache validity - if !forceRefresh { - valid, err := h.store.IsCacheValid(cacheKey) - if err == nil && valid { - return h.store.GetNotes(20) - } - } - - // Fetch from filesystem - notes, err := h.obsidianClient.GetNotes(ctx, 20) - if err != nil { - // Try to return cached data even if stale - cachedNotes, cacheErr := h.store.GetNotes(20) - if cacheErr == nil && len(cachedNotes) > 0 { - return cachedNotes, nil - } - return nil, err - } - - // Save to cache - if err := h.store.SaveNotes(notes); err != nil { - log.Printf("Failed to save notes to cache: %v", err) - } - - // Update cache metadata - if err := h.store.UpdateCacheMetadata(cacheKey, h.config.CacheTTLMinutes); err != nil { - log.Printf("Failed to update cache metadata: %v", err) - } - - return notes, nil -} - // fetchMeals fetches meals from cache or API func (h *Handler) fetchMeals(ctx context.Context, forceRefresh bool) ([]models.Meal, error) { cacheKey := store.CacheKeyPlanToEatMeals diff --git a/internal/handlers/handlers_test.go b/internal/handlers/handlers_test.go index 1aa72cc..6e9346a 100644 --- a/internal/handlers/handlers_test.go +++ b/internal/handlers/handlers_test.go @@ -353,41 +353,6 @@ func TestHandleRefresh(t *testing.T) { // The important thing is the handler doesn't error } -// TestHandleGetNotes tests the HandleGetNotes handler -func TestHandleGetNotes(t *testing.T) { - db, cleanup := setupTestDB(t) - defer cleanup() - - // Test with nil client should return empty array - cfg := &config.Config{ - CacheTTLMinutes: 5, - } - h := &Handler{ - store: db, - obsidianClient: nil, - config: cfg, - } - - req := httptest.NewRequest("GET", "/api/notes", nil) - w := httptest.NewRecorder() - - h.HandleGetNotes(w, req) - - if w.Code != http.StatusOK { - t.Errorf("Expected status 200, got %d", w.Code) - } - - var notes []models.Note - if err := json.NewDecoder(w.Body).Decode(¬es); err != nil { - t.Fatalf("Failed to decode response: %v", err) - } - - // Handler returns empty array when client is nil - if len(notes) != 0 { - t.Errorf("Expected 0 notes when client is nil, got %d", len(notes)) - } -} - // TestHandleGetMeals tests the HandleGetMeals handler func TestHandleGetMeals(t *testing.T) { db, cleanup := setupTestDB(t) diff --git a/internal/handlers/tab_state_test.go b/internal/handlers/tab_state_test.go index d3f0fce..a4f6d23 100644 --- a/internal/handlers/tab_state_test.go +++ b/internal/handlers/tab_state_test.go @@ -30,7 +30,7 @@ func TestHandleDashboard_TabState(t *testing.T) { } // Create handler - h := New(db, todoistClient, trelloClient, nil, nil, cfg) + h := New(db, todoistClient, trelloClient, nil, cfg) // Skip if templates are not loaded (test environment issue) if h.templates == nil { @@ -52,13 +52,6 @@ func TestHandleDashboard_TabState(t *testing.T) { expectedHxGet: `hx-get="/tabs/tasks"`, }, { - name: "notes tab from query param", - url: "/?tab=notes", - expectedTab: "notes", - expectedActive: `class="tab-button tab-button-active"`, - expectedHxGet: `hx-get="/tabs/notes"`, - }, - { name: "planning tab from query param", url: "/?tab=planning", expectedTab: "planning", diff --git a/internal/handlers/tabs.go b/internal/handlers/tabs.go index 1afdd06..7e0b352 100644 --- a/internal/handlers/tabs.go +++ b/internal/handlers/tabs.go @@ -126,7 +126,7 @@ func (h *TabsHandler) HandleTasks(w http.ResponseWriter, r *http.Request) { } } -// HandlePlanning renders the Planning tab (Trello boards + Todoist tasks) +// 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() @@ -136,21 +136,12 @@ func (h *TabsHandler) HandlePlanning(w http.ResponseWriter, r *http.Request) { return } - // Fetch Todoist tasks - tasks, err := h.store.GetTasks() - if err != nil { - log.Printf("Error fetching tasks: %v", err) - tasks = []models.Task{} - } - data := struct { Boards []models.Board - Tasks []models.Task Projects []models.Project }{ Boards: boards, - Tasks: tasks, - Projects: []models.Project{}, // Empty for now - form won't display but checkboxes will work + Projects: []models.Project{}, // Empty for now } if err := h.templates.ExecuteTemplate(w, "planning-tab", data); err != nil { @@ -159,49 +150,6 @@ func (h *TabsHandler) HandlePlanning(w http.ResponseWriter, r *http.Request) { } } -// 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 - Errors []string - }{ - Notes: notes, - Errors: nil, - } - - // 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 diff --git a/internal/handlers/template_test.go b/internal/handlers/template_test.go deleted file mode 100644 index b0b2378..0000000 --- a/internal/handlers/template_test.go +++ /dev/null @@ -1,40 +0,0 @@ -package handlers_test - -import ( - "html/template" - "io" - "testing" - "task-dashboard/internal/models" -) - -func TestNotesTemplateRendering(t *testing.T) { - // Parse templates (adjust paths relative to where test runs, usually package root) - // Since we run 'go test ./...', paths might need to be absolute or relative to project root if we use a helper. - // But standard 'go test' in a subdir uses that subdir as CWD. - // We will assume the test runs from 'internal/handlers'. - // So paths are "../../web/templates/..." - - tmpl, err := template.ParseGlob("../../web/templates/*.html") - if err != nil { - t.Fatalf("Failed to parse templates: %v", err) - } - tmpl, err = tmpl.ParseGlob("../../web/templates/partials/*.html") - if err != nil { - t.Fatalf("Failed to parse partials: %v", err) - } - - // Define the data structure we EXPECT to use (with Errors) - data := struct { - Notes []models.Note - Errors []string - }{ - Notes: []models.Note{}, - Errors: []string{"Test Error"}, - } - - // Execute - err = tmpl.ExecuteTemplate(io.Discard, "notes-tab", data) - if err != nil { - t.Errorf("Failed to render notes-tab with corrected data: %v", err) - } -} |
