diff options
Diffstat (limited to 'internal/handlers/handlers.go')
| -rw-r--r-- | internal/handlers/handlers.go | 177 |
1 files changed, 113 insertions, 64 deletions
diff --git a/internal/handlers/handlers.go b/internal/handlers/handlers.go index 69e1c65..e70650d 100644 --- a/internal/handlers/handlers.go +++ b/internal/handlers/handlers.go @@ -51,70 +51,7 @@ type Handler struct { // New creates a new Handler instance func New(s *store.Store, trello api.TrelloAPI, planToEat api.PlanToEatAPI, googleCalendar api.GoogleCalendarAPI, googleTasks api.GoogleTasksAPI, googleTasksOAuthConfig *oauth2.Config, claudomator api.ClaudomatorClient, cfg *config.Config, sessions *scs.SessionManager, buildVersion string, webAuthnEnabled bool) *Handler { - // Template functions - funcMap := template.FuncMap{ - "subtract": func(a, b int) int { return a - b }, - "add": func(a, b int) int { return a + b }, - // multiDayLabel returns the "(starts/ends HH:MM)" suffix for a - // multi-day calendar event's row on the day it's rendered, or "" - // for a normal item or a "spans" (pass-through) day, which shows - // as a plain all-day row with no time label. - "multiDayLabel": func(item TimelineItemView) string { - switch item.MultiDayVariant { - case "starts": - if item.Time.Hour() == 0 && item.Time.Minute() == 0 { - return "" - } - return " (starts " + item.Time.Format("3:04 PM") + ")" - case "ends": - if item.EndTime == nil || (item.EndTime.Hour() == 0 && item.EndTime.Minute() == 0) { - return "" - } - return " (ends " + item.EndTime.Format("3:04 PM") + ")" - default: - 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, ", ") - }, - } + funcMap := templateFuncMap() // Parse templates including partials tmpl, err := template.New("").Funcs(funcMap).ParseGlob(filepath.Join(cfg.TemplateDir, "*.html")) @@ -1263,3 +1200,115 @@ type ScheduledItem struct { SourceIcon string Priority int } + +// HandleTaskCommit marks a doot-native task as chosen for today, which is how +// chain steps and undated backlog work reach the day's list at all -- they carry +// no due date by design, so nothing pulls them in automatically. Posting again +// for an already-committed task is harmless (it just re-stamps today). +func (h *Handler) HandleTaskCommit(w http.ResponseWriter, r *http.Request) { + id := chi.URLParam(r, "id") + if id == "" { + JSONError(w, http.StatusBadRequest, "Missing id", nil) + return + } + if err := h.store.SetTaskCommitted(id, config.Now()); err != nil { + if errors.Is(err, store.ErrNativeTaskNotFound) { + JSONError(w, http.StatusNotFound, "task not found", err) + return + } + JSONError(w, http.StatusInternalServerError, "Failed to commit task", err) + return + } + w.Header().Set("HX-Trigger", "refresh-tasks") + w.WriteHeader(http.StatusOK) +} + +// HandleTaskUncommit drops a task back to the backlog without completing it -- +// the honest way to walk back an over-committed day. +func (h *Handler) HandleTaskUncommit(w http.ResponseWriter, r *http.Request) { + id := chi.URLParam(r, "id") + if id == "" { + JSONError(w, http.StatusBadRequest, "Missing id", nil) + return + } + if err := h.store.ClearTaskCommitted(id); err != nil { + if errors.Is(err, store.ErrNativeTaskNotFound) { + JSONError(w, http.StatusNotFound, "task not found", err) + return + } + JSONError(w, http.StatusInternalServerError, "Failed to uncommit task", err) + return + } + w.Header().Set("HX-Trigger", "refresh-tasks") + w.WriteHeader(http.StatusOK) +} + +// templateFuncMap returns the functions available to every template. Extracted +// from New so the template set can be parse-checked in a test -- New only logs a +// warning on a parse failure, so a malformed template would otherwise degrade +// silently at runtime instead of failing anything. +func templateFuncMap() template.FuncMap { + return template.FuncMap{ + "subtract": func(a, b int) int { return a - b }, + "add": func(a, b int) int { return a + b }, + // multiDayLabel returns the "(starts/ends HH:MM)" suffix for a + // multi-day calendar event's row on the day it's rendered, or "" + // for a normal item or a "spans" (pass-through) day, which shows + // as a plain all-day row with no time label. + "multiDayLabel": func(item TimelineItemView) string { + switch item.MultiDayVariant { + case "starts": + if item.Time.Hour() == 0 && item.Time.Minute() == 0 { + return "" + } + return " (starts " + item.Time.Format("3:04 PM") + ")" + case "ends": + if item.EndTime == nil || (item.EndTime.Hour() == 0 && item.EndTime.Minute() == 0) { + return "" + } + return " (ends " + item.EndTime.Format("3:04 PM") + ")" + default: + 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, ", ") + }, + } +} |
