diff options
| author | Peter Stone <thepeterstone@gmail.com> | 2026-01-12 14:28:50 -1000 |
|---|---|---|
| committer | Peter Stone <thepeterstone@gmail.com> | 2026-01-12 14:28:50 -1000 |
| commit | 06c7485a7d05de86f9898e388161e8d932d5f3e6 (patch) | |
| tree | 376083a75278c9758f53c0062742062dedb75633 /internal | |
| parent | 9ef5b7f37883f846f105da9dc5d2ba1415e594e3 (diff) | |
Modernize frontend with tabs, HTMX, and Tailwind build pipeline
Complete UI overhaul implementing modern design patterns with HTMX for
dynamic updates, proper Tailwind build pipeline, and improved UX.
Build Pipeline:
- Add npm + PostCSS + Tailwind CSS configuration
- Custom design system with brand colors
- Compiled CSS: 27KB (vs 3MB CDN), 99% reduction
- Makefile for unified build commands
- Inter font for improved typography
Tab Interface:
- Separate Tasks tab from Notes tab using HTMX
- Partial page updates without full refreshes
- Tab state management with proper refresh handling
- New endpoints: /tabs/tasks, /tabs/notes, /tabs/refresh
Template Architecture:
- Modular partials system (7 reusable components)
- Cleaner separation of concerns
Empty Board Management:
- Active boards in main 3-column grid
- Empty boards in collapsible section
- Reduces visual clutter
Visual Design Enhancements:
- Inter font, brand color accents
- Improved typography hierarchy and spacing
- Enhanced card styling with hover effects
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Diffstat (limited to 'internal')
| -rw-r--r-- | internal/handlers/handlers.go | 67 |
1 files changed, 66 insertions, 1 deletions
diff --git a/internal/handlers/handlers.go b/internal/handlers/handlers.go index 6872ba7..f31fc56 100644 --- a/internal/handlers/handlers.go +++ b/internal/handlers/handlers.go @@ -28,12 +28,18 @@ type Handler struct { // New creates a new Handler instance func New(store *store.Store, todoist api.TodoistAPI, trello api.TrelloAPI, obsidian api.ObsidianAPI, planToEat api.PlanToEatAPI, cfg *config.Config) *Handler { - // Parse templates + // Parse templates including partials tmpl, err := template.ParseGlob("web/templates/*.html") if err != nil { log.Printf("Warning: failed to parse templates: %v", err) } + // Also parse partials + tmpl, err = tmpl.ParseGlob("web/templates/partials/*.html") + if err != nil { + log.Printf("Warning: failed to parse partial templates: %v", err) + } + return &Handler{ store: store, todoistClient: todoist, @@ -136,6 +142,65 @@ func (h *Handler) HandleGetBoards(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(boards) } +// HandleTasksTab renders the tasks tab content (Trello + Todoist + PlanToEat) +func (h *Handler) HandleTasksTab(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + data, err := h.aggregateData(ctx, false) + if err != nil { + http.Error(w, "Failed to load tasks", http.StatusInternalServerError) + log.Printf("Error loading tasks tab: %v", err) + return + } + + if err := h.templates.ExecuteTemplate(w, "tasks-tab", data); err != nil { + http.Error(w, "Failed to render template", http.StatusInternalServerError) + log.Printf("Error rendering tasks tab: %v", err) + } +} + +// HandleNotesTab renders the notes tab content (Obsidian) +func (h *Handler) HandleNotesTab(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + data, err := h.aggregateData(ctx, false) + if err != nil { + http.Error(w, "Failed to load notes", http.StatusInternalServerError) + log.Printf("Error loading notes tab: %v", err) + return + } + + if err := h.templates.ExecuteTemplate(w, "notes-tab", data); err != nil { + http.Error(w, "Failed to render template", http.StatusInternalServerError) + log.Printf("Error rendering notes tab: %v", err) + } +} + +// HandleRefreshTab refreshes and re-renders the specified tab +func (h *Handler) HandleRefreshTab(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + tab := r.URL.Query().Get("tab") // "tasks" or "notes" + + // Force refresh + data, err := h.aggregateData(ctx, true) + if err != nil { + http.Error(w, "Failed to refresh", http.StatusInternalServerError) + log.Printf("Error refreshing tab: %v", err) + return + } + + // Determine template to render + templateName := "tasks-tab" + if tab == "notes" { + templateName = "notes-tab" + } + + if err := h.templates.ExecuteTemplate(w, templateName, data); err != nil { + http.Error(w, "Failed to render template", http.StatusInternalServerError) + log.Printf("Error rendering refreshed tab: %v", err) + } +} + // aggregateData fetches and caches data from all sources func (h *Handler) aggregateData(ctx context.Context, forceRefresh bool) (*models.DashboardData, error) { data := &models.DashboardData{ |
