1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
|
package handlers
import (
"errors"
"net/http"
"github.com/go-chi/chi/v5"
"task-dashboard/internal/models"
"task-dashboard/internal/store"
)
// BuildChainSummaries returns one summary per active/paused chain --
// project name, the currently-unlocked task (nil if none, e.g. a chain
// that just completed and hasn't been re-fetched into the active/paused
// set yet), and 1-indexed position/total for a "N/M" progress display.
// Backs the Tasks tab's Chains section.
func BuildChainSummaries(s *store.Store) ([]models.ChainSummary, error) {
chains, err := s.GetChains()
if err != nil {
return nil, err
}
summaries := make([]models.ChainSummary, 0, len(chains))
for _, chain := range chains {
project, err := s.GetProjectByID(chain.ProjectID)
if err != nil {
continue // orphaned project reference -- skip rather than fail the whole tab
}
tasks, err := s.GetChainTasks(chain.ID)
if err != nil {
return nil, err
}
summary := models.ChainSummary{Chain: chain, ProjectName: project.Name, Total: len(tasks)}
for i, t := range tasks {
if t.ChainUnlocked {
task := t
summary.CurrentTask = &task
summary.Position = i + 1
break
}
}
summaries = append(summaries, summary)
}
return summaries, nil
}
// HandleChainDetailView renders the full ordered checklist for a chain --
// locked and unlocked tasks both -- for the web Tasks tab's chain modal.
// This is the "visible in the tasks list" requirement from the chains
// design spec, met on web (the spec originally scoped this to Android
// only; extended to web per user request).
func (h *Handler) HandleChainDetailView(w http.ResponseWriter, r *http.Request) {
id := chi.URLParam(r, "id")
chain, err := h.store.GetChain(id)
if err != nil {
if errors.Is(err, store.ErrNativeTaskNotFound) {
http.Error(w, "chain not found", http.StatusNotFound)
return
}
http.Error(w, "internal error", http.StatusInternalServerError)
return
}
project, err := h.store.GetProjectByID(chain.ProjectID)
if err != nil {
http.Error(w, "internal error", http.StatusInternalServerError)
return
}
tasks, err := h.store.GetChainTasks(id)
if err != nil {
http.Error(w, "internal error", http.StatusInternalServerError)
return
}
data := struct {
Chain models.Chain
ProjectName string
Tasks []models.Task
}{Chain: *chain, ProjectName: project.Name, Tasks: tasks}
HTMLResponse(w, h.renderer, "chain-detail", data)
}
// HandleChainPause, HandleChainResume, and HandleChainAbandon are the
// session-authed web equivalents of the widget API's chain-status
// endpoints, for the Tasks tab's chain modal.
func (h *Handler) HandleChainPause(w http.ResponseWriter, r *http.Request) {
h.setChainStatusWeb(w, r, "paused")
}
func (h *Handler) HandleChainResume(w http.ResponseWriter, r *http.Request) {
h.setChainStatusWeb(w, r, "active")
}
func (h *Handler) HandleChainAbandon(w http.ResponseWriter, r *http.Request) {
h.setChainStatusWeb(w, r, "abandoned")
}
func (h *Handler) setChainStatusWeb(w http.ResponseWriter, r *http.Request, status string) {
id := chi.URLParam(r, "id")
if err := h.store.SetChainStatus(id, status); err != nil {
if errors.Is(err, store.ErrNativeTaskNotFound) {
http.Error(w, "chain not found", http.StatusNotFound)
return
}
http.Error(w, "failed to update chain", http.StatusInternalServerError)
return
}
w.Header().Set("HX-Reswap", "none")
w.Header().Set("HX-Trigger", "refresh-tasks")
w.WriteHeader(http.StatusOK)
}
|