From 2e739638477e87a1b1df662740f191c86db60186 Mon Sep 17 00:00:00 2001 From: Peter Stone Date: Mon, 26 Jan 2026 08:10:27 -1000 Subject: Phase 5: Extract functions to reduce complexity - Create atoms.go with BuildUnifiedAtomList, SortAtomsByUrgency, PartitionAtomsByTime - Create helpers.go with parseFormOr400, requireFormValue - Refactor HandleTabTasks from 95 lines to 25 lines using extracted functions - Remove duplicate atomUrgencyTier function - Update handlers to use parseFormOr400 helper Co-Authored-By: Claude Opus 4.5 --- internal/handlers/atoms.go | 112 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 internal/handlers/atoms.go (limited to 'internal/handlers/atoms.go') diff --git a/internal/handlers/atoms.go b/internal/handlers/atoms.go new file mode 100644 index 0000000..7bc4465 --- /dev/null +++ b/internal/handlers/atoms.go @@ -0,0 +1,112 @@ +package handlers + +import ( + "sort" + + "task-dashboard/internal/models" + "task-dashboard/internal/store" +) + +// BuildUnifiedAtomList creates a list of atoms from tasks, cards, and bugs +func BuildUnifiedAtomList(s *store.Store) ([]models.Atom, []models.Board, error) { + tasks, err := s.GetTasks() + if err != nil { + return nil, nil, err + } + + boards, err := s.GetBoards() + if err != nil { + return nil, nil, err + } + + bugs, _ := s.GetUnresolvedBugs() // Ignore error, bugs are optional + + atoms := make([]models.Atom, 0, len(tasks)+len(bugs)) + + // Add incomplete tasks + for _, task := range tasks { + if !task.Completed { + atoms = append(atoms, models.TaskToAtom(task)) + } + } + + // Add cards with due dates or from actionable lists + for _, board := range boards { + for _, card := range board.Cards { + if card.DueDate != nil || isActionableList(card.ListName) { + atoms = append(atoms, models.CardToAtom(card)) + } + } + } + + // Add unresolved bugs + for _, bug := range bugs { + atoms = append(atoms, models.BugToAtom(models.Bug{ + ID: bug.ID, + Description: bug.Description, + CreatedAt: bug.CreatedAt, + })) + } + + // Compute UI fields for all atoms + for i := range atoms { + atoms[i].ComputeUIFields() + } + + return atoms, boards, nil +} + +// SortAtomsByUrgency sorts atoms by urgency tier, then due date, then priority +func SortAtomsByUrgency(atoms []models.Atom) { + sort.SliceStable(atoms, func(i, j int) bool { + tierI := atomUrgencyTier(atoms[i]) + tierJ := atomUrgencyTier(atoms[j]) + + if tierI != tierJ { + return tierI < tierJ + } + + 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) + } + } + + return atoms[i].Priority > atoms[j].Priority + }) +} + +// PartitionAtomsByTime separates atoms into current and future lists +// Recurring tasks that are future are excluded entirely +func PartitionAtomsByTime(atoms []models.Atom) (current, future []models.Atom) { + for _, a := range atoms { + // Don't show recurring tasks until the day they're due + if a.IsRecurring && a.IsFuture { + continue + } + if a.IsFuture { + future = append(future, a) + } else { + current = append(current, a) + } + } + return +} + +// atomUrgencyTier returns an urgency tier for sorting: +// 0 = overdue, 1 = today with specific time, 2 = today no time, 3 = future, 4 = no due date +func atomUrgencyTier(a models.Atom) int { + if a.DueDate == nil { + return 4 + } + if a.IsOverdue { + return 0 + } + if a.IsFuture { + return 3 + } + if a.HasSetTime { + return 1 + } + return 2 +} -- cgit v1.2.3