summaryrefslogtreecommitdiff
path: root/internal/handlers
diff options
context:
space:
mode:
Diffstat (limited to 'internal/handlers')
-rw-r--r--internal/handlers/handlers.go43
-rw-r--r--internal/handlers/heuristic_test.go97
-rw-r--r--internal/handlers/tabs.go17
3 files changed, 155 insertions, 2 deletions
diff --git a/internal/handlers/handlers.go b/internal/handlers/handlers.go
index c3e49ed..9ba6351 100644
--- a/internal/handlers/handlers.go
+++ b/internal/handlers/handlers.go
@@ -6,6 +6,8 @@ import (
"html/template"
"log"
"net/http"
+ "sort"
+ "strings"
"sync"
"time"
@@ -302,6 +304,47 @@ func (h *Handler) aggregateData(ctx context.Context, forceRefresh bool) (*models
wg.Wait()
+ // Filter Trello cards into tasks based on heuristic
+ var trelloTasks []models.Card
+ for _, board := range data.Boards {
+ for _, card := range board.Cards {
+ listNameLower := strings.ToLower(card.ListName)
+ isTask := card.DueDate != nil ||
+ strings.Contains(listNameLower, "todo") ||
+ strings.Contains(listNameLower, "doing") ||
+ strings.Contains(listNameLower, "progress") ||
+ strings.Contains(listNameLower, "task")
+
+ if isTask {
+ trelloTasks = append(trelloTasks, card)
+ }
+ }
+ }
+
+ // Sort trelloTasks: earliest due date first, nil last, then by board name
+ sort.Slice(trelloTasks, func(i, j int) bool {
+ // Both have due dates: compare dates
+ if trelloTasks[i].DueDate != nil && trelloTasks[j].DueDate != nil {
+ if !trelloTasks[i].DueDate.Equal(*trelloTasks[j].DueDate) {
+ return trelloTasks[i].DueDate.Before(*trelloTasks[j].DueDate)
+ }
+ // Same due date, fall through to board name comparison
+ }
+
+ // Only one has due date: that one comes first
+ if trelloTasks[i].DueDate != nil && trelloTasks[j].DueDate == nil {
+ return true
+ }
+ if trelloTasks[i].DueDate == nil && trelloTasks[j].DueDate != nil {
+ return false
+ }
+
+ // Both nil or same due date: sort by board name
+ return trelloTasks[i].BoardName < trelloTasks[j].BoardName
+ })
+
+ data.TrelloTasks = trelloTasks
+
return data, nil
}
diff --git a/internal/handlers/heuristic_test.go b/internal/handlers/heuristic_test.go
new file mode 100644
index 0000000..f76fdc0
--- /dev/null
+++ b/internal/handlers/heuristic_test.go
@@ -0,0 +1,97 @@
+package handlers
+
+import (
+ "net/http/httptest"
+ "os"
+ "strings"
+ "testing"
+ "time"
+
+ "task-dashboard/internal/models"
+ "task-dashboard/internal/store"
+)
+
+func TestHandleTasks_Heuristic(t *testing.T) {
+ // Create temp database file
+ tmpFile, err := os.CreateTemp("", "test_heuristic_*.db")
+ if err != nil {
+ t.Fatalf("Failed to create temp db: %v", err)
+ }
+ tmpFile.Close()
+ defer os.Remove(tmpFile.Name())
+
+ // Save current directory and change to project root
+ originalDir, err := os.Getwd()
+ if err != nil {
+ t.Fatalf("Failed to get working directory: %v", err)
+ }
+
+ // Change to project root (2 levels up from internal/handlers)
+ if err := os.Chdir("../../"); err != nil {
+ t.Fatalf("Failed to change to project root: %v", err)
+ }
+ defer os.Chdir(originalDir)
+
+ // Initialize store (this runs migrations)
+ db, err := store.New(tmpFile.Name())
+ if err != nil {
+ t.Fatalf("Failed to initialize store: %v", err)
+ }
+ defer db.Close()
+
+ // Seed Data
+ // Board 1: Has actionable lists
+ board1 := models.Board{ID: "b1", Name: "Work Board"}
+
+ // Card 1: Has Due Date (Should appear)
+ due := time.Now()
+ card1 := models.Card{ID: "c1", Name: "Due Task", ListID: "l1", ListName: "Backlog", DueDate: &due, BoardName: "Work Board"}
+
+ // Card 2: No Due Date, Actionable List (Should appear)
+ card2 := models.Card{ID: "c2", Name: "Doing Task", ListID: "l2", ListName: "Doing", BoardName: "Work Board"}
+
+ // Card 3: No Due Date, Non-Actionable List (Should NOT appear)
+ card3 := models.Card{ID: "c3", Name: "Backlog Task", ListID: "l1", ListName: "Backlog", BoardName: "Work Board"}
+
+ // Card 4: No Due Date, "To Do" List (Should appear)
+ card4 := models.Card{ID: "c4", Name: "Todo Task", ListID: "l3", ListName: "To Do", BoardName: "Work Board"}
+
+ board1.Cards = []models.Card{card1, card2, card3, card4}
+
+ if err := db.SaveBoards([]models.Board{board1}); err != nil {
+ t.Fatalf("Failed to save boards: %v", err)
+ }
+
+ // Create Handler
+ h := NewTabsHandler(db)
+
+ // Skip if templates are not loaded
+ if h.templates == nil {
+ t.Skip("Templates not available in test environment")
+ }
+
+ req := httptest.NewRequest("GET", "/tabs/tasks", nil)
+ w := httptest.NewRecorder()
+
+ // Execute
+ h.HandleTasks(w, req)
+
+ // Verify
+ resp := w.Body.String()
+
+ // Check for presence of expected tasks
+ if !strings.Contains(resp, "Due Task") {
+ t.Errorf("Expected 'Due Task' to be present")
+ }
+ if !strings.Contains(resp, "Doing Task") {
+ t.Errorf("Expected 'Doing Task' to be present")
+ }
+ if !strings.Contains(resp, "Todo Task") {
+ t.Errorf("Expected 'Todo Task' to be present")
+ }
+
+ // Check for absence of non-expected tasks
+ if strings.Contains(resp, "Backlog Task") {
+ t.Errorf("Expected 'Backlog Task' to be ABSENT")
+ }
+}
diff --git a/internal/handlers/tabs.go b/internal/handlers/tabs.go
index c23910d..ce9c34f 100644
--- a/internal/handlers/tabs.go
+++ b/internal/handlers/tabs.go
@@ -5,12 +5,25 @@ import (
"log"
"net/http"
"sort"
+ "strings"
"time"
"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")
+}
+
// TabsHandler handles tab-specific rendering with Atom model
type TabsHandler struct {
store *store.Store
@@ -65,10 +78,10 @@ func (h *TabsHandler) HandleTasks(w http.ResponseWriter, r *http.Request) {
}
}
- // Convert Trello cards with due dates
+ // Convert Trello cards with due dates or in actionable lists
for _, board := range boards {
for _, card := range board.Cards {
- if card.DueDate != nil {
+ if card.DueDate != nil || isActionableList(card.ListName) {
atoms = append(atoms, models.CardToAtom(card))
}
}