summaryrefslogtreecommitdiff
path: root/internal/handlers/handlers.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/handlers/handlers.go')
-rw-r--r--internal/handlers/handlers.go160
1 files changed, 143 insertions, 17 deletions
diff --git a/internal/handlers/handlers.go b/internal/handlers/handlers.go
index a5bc51a..ed15e89 100644
--- a/internal/handlers/handlers.go
+++ b/internal/handlers/handlers.go
@@ -10,6 +10,7 @@ import (
"net/http"
"path/filepath"
"sort"
+ "strconv"
"strings"
"sync"
"time"
@@ -68,6 +69,45 @@ func New(s *store.Store, trello api.TrelloAPI, planToEat api.PlanToEatAPI, googl
return ""
}
},
+ // hasInt reports whether n is present in a []int -- used to pre-check
+ // the weekday checkboxes on a recurring task's edit form.
+ "hasInt": func(list []int, n int) bool {
+ for _, v := range list {
+ if v == n {
+ return true
+ }
+ }
+ return false
+ },
+ // recurrenceUnit maps a recurrence freq to its singular noun, so the
+ // task-detail template can say "every 2 weeks" instead of gluing an
+ // "s" onto the adjective "weekly" ("every 2 weeklys").
+ "recurrenceUnit": func(freq string) string {
+ switch freq {
+ case "daily":
+ return "day"
+ case "weekly":
+ return "week"
+ case "monthly":
+ return "month"
+ case "yearly":
+ return "year"
+ default:
+ return freq
+ }
+ },
+ // weekdayNames formats recurrence weekday indices (0=Sunday, matching
+ // time.Weekday, see models/recurrence.go) as "Mon, Wed, Fri".
+ "weekdayNames": func(weekdays []int) string {
+ names := [7]string{"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}
+ parts := make([]string, 0, len(weekdays))
+ for _, d := range weekdays {
+ if d >= 0 && d < 7 {
+ parts = append(parts, names[d])
+ }
+ }
+ return strings.Join(parts, ", ")
+ },
}
// Parse templates including partials
@@ -752,37 +792,102 @@ func (h *Handler) HandleGetListsOptions(w http.ResponseWriter, r *http.Request)
HTMLResponse(w, h.renderer, "lists-options", lists)
}
-// HandleGetTaskDetail returns task details as HTML for modal
-func (h *Handler) HandleGetTaskDetail(w http.ResponseWriter, r *http.Request) {
- id := r.URL.Query().Get("id")
- source := r.URL.Query().Get("source")
-
- if id == "" || source == "" {
- JSONError(w, http.StatusBadRequest, "Missing id or source", nil)
- return
- }
+// taskDetailData is what the "task-detail" template renders (the modal
+// opened from any tab's task card). Recurrence fields are doot-native only.
+type taskDetailData struct {
+ Title string
+ ID string
+ Source string
+ Description string
+ IsDoot bool
+ RecurrenceFreq string
+ RecurrenceInterval int
+ RecurrenceWeekdays []int
+}
- var title, description string
+// loadTaskDetailData fetches a task's current fields for the detail modal,
+// shared between the initial GET and re-rendering after a recurrence edit.
+func (h *Handler) loadTaskDetailData(id, source string) taskDetailData {
+ data := taskDetailData{ID: id, Source: source}
switch source {
case "trello":
if boards, err := h.store.GetBoards(); err == nil {
for _, b := range boards {
for _, c := range b.Cards {
if c.ID == id {
- title, description = c.Name, c.Description
+ data.Title, data.Description = c.Name, c.Description
break
}
}
}
}
+ case "doot":
+ if task, err := h.store.GetNativeTaskByID(id); err == nil {
+ data.IsDoot = true
+ data.Title = task.Content
+ data.Description = task.Description
+ data.RecurrenceFreq = task.RecurrenceFreq
+ data.RecurrenceInterval = task.RecurrenceInterval
+ data.RecurrenceWeekdays = task.RecurrenceWeekdays
+ }
}
+ return data
+}
- HTMLResponse(w, h.renderer, "task-detail", struct {
- Title string
- ID string
- Source string
- Description string
- }{title, id, source, description})
+// HandleGetTaskDetail returns task details as HTML for modal.
+func (h *Handler) HandleGetTaskDetail(w http.ResponseWriter, r *http.Request) {
+ id := r.URL.Query().Get("id")
+ source := r.URL.Query().Get("source")
+
+ if id == "" || source == "" {
+ JSONError(w, http.StatusBadRequest, "Missing id or source", nil)
+ return
+ }
+
+ HTMLResponse(w, h.renderer, "task-detail", h.loadTaskDetailData(id, source))
+}
+
+// HandleSetTaskRecurrence sets or clears a doot-native task's recurrence
+// pattern from the web task-detail modal -- the HTMX counterpart to the
+// widget API's HandleWidgetTaskRecurrence (internal/handlers/widget.go).
+// freq == "" clears recurrence. Re-renders the detail modal so it reflects
+// the new state without closing.
+func (h *Handler) HandleSetTaskRecurrence(w http.ResponseWriter, r *http.Request) {
+ if err := r.ParseForm(); err != nil {
+ JSONError(w, http.StatusBadRequest, "Failed to parse form", err)
+ return
+ }
+ id := r.FormValue("id")
+ if id == "" {
+ JSONError(w, http.StatusBadRequest, "Missing id", nil)
+ return
+ }
+ freq := r.FormValue("freq")
+ if freq != "" && freq != "daily" && freq != "weekly" && freq != "monthly" && freq != "yearly" {
+ JSONError(w, http.StatusBadRequest, "Invalid freq", nil)
+ return
+ }
+ interval, _ := strconv.Atoi(r.FormValue("interval"))
+ if interval <= 0 {
+ interval = 1
+ }
+ var weekdays []int
+ for _, d := range r.Form["weekdays"] {
+ if n, err := strconv.Atoi(d); err == nil {
+ weekdays = append(weekdays, n)
+ }
+ }
+
+ if err := h.store.SetTaskRecurrence(id, freq, interval, weekdays); err != nil {
+ if errors.Is(err, store.ErrNativeTaskNotFound) {
+ JSONError(w, http.StatusNotFound, "task not found", err)
+ return
+ }
+ JSONError(w, http.StatusInternalServerError, "Failed to update recurrence", err)
+ return
+ }
+
+ HTMLResponse(w, h.renderer, "task-detail", h.loadTaskDetailData(id, "doot"))
}
// HandleTaskDetailPage renders a standalone full-page task detail view (used by Android widget deep-links).
@@ -889,6 +994,21 @@ func (h *Handler) HandleTabTasks(w http.ResponseWriter, r *http.Request) {
log.Printf("Warning: failed to build chain summaries: %v", err)
}
+ bucketSummaries, err := BuildBucketSummaries(h.store)
+ if err != nil {
+ log.Printf("Warning: failed to build bucket summaries: %v", err)
+ }
+
+ projects, err := h.store.GetProjects()
+ if err != nil {
+ log.Printf("Warning: failed to load projects: %v", err)
+ }
+
+ labels, err := h.store.GetLabelColors()
+ if err != nil {
+ log.Printf("Warning: failed to load label colors: %v", err)
+ }
+
SortAtomsByUrgency(atoms)
currentAtoms, futureAtoms := PartitionAtomsByTime(atoms)
@@ -897,12 +1017,18 @@ func (h *Handler) HandleTabTasks(w http.ResponseWriter, r *http.Request) {
FutureAtoms []models.Atom
Boards []models.Board
Chains []models.ChainSummary
+ Buckets []models.BucketSummary
+ Projects []models.Project
+ Labels []models.LabelColor
Today string
}{
Atoms: currentAtoms,
FutureAtoms: futureAtoms,
Boards: boards,
Chains: chainSummaries,
+ Buckets: bucketSummaries,
+ Projects: projects,
+ Labels: labels,
Today: config.Now().Format("2006-01-02"),
}