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.go152
1 files changed, 145 insertions, 7 deletions
diff --git a/internal/handlers/handlers.go b/internal/handlers/handlers.go
index a1a12e7..115d903 100644
--- a/internal/handlers/handlers.go
+++ b/internal/handlers/handlers.go
@@ -6,12 +6,15 @@ import (
"html/template"
"log"
"net/http"
+ "net/url"
"path/filepath"
"sort"
"strings"
"sync"
"time"
+ "github.com/go-chi/chi/v5"
+
"task-dashboard/internal/api"
"task-dashboard/internal/auth"
"task-dashboard/internal/config"
@@ -1147,25 +1150,51 @@ func (h *Handler) HandleShoppingQuickAdd(w http.ResponseWriter, r *http.Request)
}
name := strings.TrimSpace(r.FormValue("name"))
- store := strings.TrimSpace(r.FormValue("store"))
+ storeName := strings.TrimSpace(r.FormValue("store"))
+ mode := r.FormValue("mode") // "shopping-mode" if from shopping mode page
if name == "" {
JSONError(w, http.StatusBadRequest, "Name is required", nil)
return
}
- if store == "" {
- store = "Quick Add"
+ if storeName == "" {
+ storeName = "Quick Add"
}
- if err := h.store.SaveUserShoppingItem(name, store); err != nil {
+ if err := h.store.SaveUserShoppingItem(name, storeName); err != nil {
JSONError(w, http.StatusInternalServerError, "Failed to save item", err)
return
}
- // Return refreshed shopping tab
ctx := r.Context()
- stores := h.aggregateShoppingLists(ctx)
- HTMLResponse(w, h.templates, "shopping-tab", struct{ Stores []models.ShoppingStore }{stores})
+ allStores := h.aggregateShoppingLists(ctx)
+
+ // If called from shopping mode, return just the items for that store
+ if mode == "shopping-mode" {
+ var items []models.UnifiedShoppingItem
+ for _, store := range allStores {
+ if strings.EqualFold(store.Name, storeName) {
+ for _, cat := range store.Categories {
+ items = append(items, cat.Items...)
+ }
+ }
+ }
+ // Sort: unchecked first, then checked
+ sort.Slice(items, func(i, j int) bool {
+ if items[i].Checked != items[j].Checked {
+ return !items[i].Checked
+ }
+ return items[i].Name < items[j].Name
+ })
+ HTMLResponse(w, h.templates, "shopping-mode-items", struct {
+ StoreName string
+ Items []models.UnifiedShoppingItem
+ }{storeName, items})
+ return
+ }
+
+ // Return refreshed shopping tab
+ HTMLResponse(w, h.templates, "shopping-tab", struct{ Stores []models.ShoppingStore }{allStores})
}
// HandleShoppingToggle toggles a shopping item's checked state
@@ -1206,6 +1235,115 @@ func (h *Handler) HandleShoppingToggle(w http.ResponseWriter, r *http.Request) {
HTMLResponse(w, h.templates, "shopping-tab", struct{ Stores []models.ShoppingStore }{stores})
}
+// HandleShoppingMode renders the focused shopping mode for a single store
+func (h *Handler) HandleShoppingMode(w http.ResponseWriter, r *http.Request) {
+ storeName := chi.URLParam(r, "store")
+ if storeName == "" {
+ http.Redirect(w, r, "/?tab=shopping", http.StatusFound)
+ return
+ }
+
+ // URL decode the store name
+ storeName, _ = url.QueryUnescape(storeName)
+
+ ctx := r.Context()
+ allStores := h.aggregateShoppingLists(ctx)
+
+ // Find the requested store
+ var items []models.UnifiedShoppingItem
+ var storeNames []string
+ for _, store := range allStores {
+ storeNames = append(storeNames, store.Name)
+ if strings.EqualFold(store.Name, storeName) {
+ // Flatten categories into single item list
+ for _, cat := range store.Categories {
+ items = append(items, cat.Items...)
+ }
+ }
+ }
+
+ // Sort: unchecked first, then checked (both alphabetically within their group)
+ sort.Slice(items, func(i, j int) bool {
+ if items[i].Checked != items[j].Checked {
+ return !items[i].Checked // unchecked items first
+ }
+ return items[i].Name < items[j].Name
+ })
+
+ data := struct {
+ StoreName string
+ Items []models.UnifiedShoppingItem
+ StoreNames []string
+ }{
+ StoreName: storeName,
+ Items: items,
+ StoreNames: storeNames,
+ }
+
+ HTMLResponse(w, h.templates, "shopping-mode", data)
+}
+
+// HandleShoppingModeToggle toggles an item in shopping mode and returns updated list
+func (h *Handler) HandleShoppingModeToggle(w http.ResponseWriter, r *http.Request) {
+ storeName := chi.URLParam(r, "store")
+ if err := r.ParseForm(); err != nil {
+ JSONError(w, http.StatusBadRequest, "Failed to parse form", err)
+ return
+ }
+
+ id := r.FormValue("id")
+ source := r.FormValue("source")
+ checked := r.FormValue("checked") == "true"
+
+ // Toggle the item
+ switch source {
+ case "user":
+ var userID int64
+ if _, err := fmt.Sscanf(id, "user-%d", &userID); err != nil {
+ JSONError(w, http.StatusBadRequest, "Invalid user item ID", err)
+ return
+ }
+ if err := h.store.ToggleUserShoppingItem(userID, checked); err != nil {
+ JSONError(w, http.StatusInternalServerError, "Failed to toggle item", err)
+ return
+ }
+ case "trello", "plantoeat":
+ if err := h.store.SetShoppingItemChecked(source, id, checked); err != nil {
+ JSONError(w, http.StatusInternalServerError, "Failed to toggle item", err)
+ return
+ }
+ }
+
+ // URL decode the store name
+ storeName, _ = url.QueryUnescape(storeName)
+
+ // Return updated item list partial
+ ctx := r.Context()
+ allStores := h.aggregateShoppingLists(ctx)
+
+ var items []models.UnifiedShoppingItem
+ for _, store := range allStores {
+ if strings.EqualFold(store.Name, storeName) {
+ for _, cat := range store.Categories {
+ items = append(items, cat.Items...)
+ }
+ }
+ }
+
+ // Sort: unchecked first, then checked
+ sort.Slice(items, func(i, j int) bool {
+ if items[i].Checked != items[j].Checked {
+ return !items[i].Checked
+ }
+ return items[i].Name < items[j].Name
+ })
+
+ HTMLResponse(w, h.templates, "shopping-mode-items", struct {
+ StoreName string
+ Items []models.UnifiedShoppingItem
+ }{storeName, items})
+}
+
// aggregateShoppingLists combines Trello, PlanToEat, and user shopping items by store
func (h *Handler) aggregateShoppingLists(ctx context.Context) []models.ShoppingStore {
storeMap := make(map[string]map[string][]models.UnifiedShoppingItem)