diff options
Diffstat (limited to 'internal/handlers/handlers.go')
| -rw-r--r-- | internal/handlers/handlers.go | 160 |
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"), } |
