package handlers import ( "encoding/json" "net/http" "github.com/go-chi/chi/v5" "task-dashboard/internal/models" ) // HandleSettingsPage renders the settings page func (h *Handler) HandleSettingsPage(w http.ResponseWriter, r *http.Request) { configs, _ := h.store.GetSourceConfigs() toggles, _ := h.store.GetFeatureToggles() // Group configs by source bySource := make(map[string][]models.SourceConfig) for _, cfg := range configs { bySource[cfg.Source] = append(bySource[cfg.Source], cfg) } data := struct { Configs map[string][]models.SourceConfig Sources []string Toggles []models.FeatureToggle }{ Configs: bySource, Sources: []string{"trello", "todoist", "gcal", "gtasks"}, Toggles: toggles, } if err := h.renderer.Render(w, "settings.html", data); err != nil { JSONError(w, http.StatusInternalServerError, "Failed to render settings", err) } } // HandleSyncSources fetches available items from all sources and syncs to config func (h *Handler) HandleSyncSources(w http.ResponseWriter, r *http.Request) { ctx := r.Context() // Sync Trello boards if h.trelloClient != nil { boards, err := h.trelloClient.GetBoards(ctx) if err == nil { var items []models.SourceConfig for _, b := range boards { items = append(items, models.SourceConfig{ Source: "trello", ItemType: "board", ItemID: b.ID, ItemName: b.Name, }) } _ = h.store.SyncSourceConfigs("trello", "board", items) } } // Sync Todoist projects if h.todoistClient != nil { projects, err := h.todoistClient.GetProjects(ctx) if err == nil { var items []models.SourceConfig for _, p := range projects { items = append(items, models.SourceConfig{ Source: "todoist", ItemType: "project", ItemID: p.ID, ItemName: p.Name, }) } _ = h.store.SyncSourceConfigs("todoist", "project", items) } } // Sync Google Calendar calendars if h.googleCalendarClient != nil { calendars, err := h.googleCalendarClient.GetCalendarList(ctx) if err == nil { var items []models.SourceConfig for _, c := range calendars { items = append(items, models.SourceConfig{ Source: "gcal", ItemType: "calendar", ItemID: c.ID, ItemName: c.Name, }) } _ = h.store.SyncSourceConfigs("gcal", "calendar", items) } } // Sync Google Tasks lists if h.googleTasksClient != nil { lists, err := h.googleTasksClient.GetTaskLists(ctx) if err == nil { var items []models.SourceConfig for _, l := range lists { items = append(items, models.SourceConfig{ Source: "gtasks", ItemType: "tasklist", ItemID: l.ID, ItemName: l.Name, }) } _ = h.store.SyncSourceConfigs("gtasks", "tasklist", items) } } // Return updated configs h.HandleSettingsPage(w, r) } // HandleToggleSourceConfig toggles a source config item func (h *Handler) HandleToggleSourceConfig(w http.ResponseWriter, r *http.Request) { if err := r.ParseForm(); err != nil { JSONError(w, http.StatusBadRequest, "Failed to parse form", err) return } source := r.FormValue("source") itemType := r.FormValue("item_type") itemID := r.FormValue("item_id") enabled := r.FormValue("enabled") == "true" if source == "" || itemType == "" || itemID == "" { JSONError(w, http.StatusBadRequest, "Missing required fields", nil) return } if err := h.store.SetSourceConfigEnabled(source, itemType, itemID, enabled); err != nil { JSONError(w, http.StatusInternalServerError, "Failed to update config", err) return } // Invalidate relevant cache switch source { case "trello": _ = h.store.InvalidateCache("trello_boards") case "todoist": _ = h.store.InvalidateCache("todoist_tasks") } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]bool{"enabled": enabled}) } // HandleGetSourceOptions returns available options for a source (HTMX partial) func (h *Handler) HandleGetSourceOptions(w http.ResponseWriter, r *http.Request) { source := chi.URLParam(r, "source") if source == "" { JSONError(w, http.StatusBadRequest, "Source required", nil) return } configs, err := h.store.GetSourceConfigsBySource(source) if err != nil { JSONError(w, http.StatusInternalServerError, "Failed to load configs", err) return } data := struct { Source string Configs []models.SourceConfig }{source, configs} HTMLResponse(w, h.renderer, "settings-source-options", data) } // HandleToggleFeature toggles a feature flag func (h *Handler) HandleToggleFeature(w http.ResponseWriter, r *http.Request) { if err := r.ParseForm(); err != nil { JSONError(w, http.StatusBadRequest, "Failed to parse form", err) return } name := r.FormValue("name") enabled := r.FormValue("enabled") == "true" if name == "" { JSONError(w, http.StatusBadRequest, "Feature name required", nil) return } if err := h.store.SetFeatureEnabled(name, enabled); err != nil { JSONError(w, http.StatusInternalServerError, "Failed to update feature", err) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]bool{"enabled": enabled}) } // HandleCreateFeature creates a new feature toggle func (h *Handler) HandleCreateFeature(w http.ResponseWriter, r *http.Request) { if err := r.ParseForm(); err != nil { JSONError(w, http.StatusBadRequest, "Failed to parse form", err) return } name := r.FormValue("name") description := r.FormValue("description") if name == "" { JSONError(w, http.StatusBadRequest, "Feature name required", nil) return } if err := h.store.CreateFeatureToggle(name, description, false); err != nil { JSONError(w, http.StatusInternalServerError, "Failed to create feature", err) return } // Return updated toggles list h.HandleSettingsPage(w, r) } // HandleDeleteFeature removes a feature toggle func (h *Handler) HandleDeleteFeature(w http.ResponseWriter, r *http.Request) { name := chi.URLParam(r, "name") if name == "" { JSONError(w, http.StatusBadRequest, "Feature name required", nil) return } if err := h.store.DeleteFeatureToggle(name); err != nil { JSONError(w, http.StatusInternalServerError, "Failed to delete feature", err) return } w.WriteHeader(http.StatusOK) }