diff options
| -rw-r--r-- | internal/handlers/handlers.go | 17 | ||||
| -rw-r--r-- | internal/handlers/tab_state_test.go | 106 | ||||
| -rw-r--r-- | web/static/js/app.js | 5 | ||||
| -rw-r--r-- | web/templates/index.html | 18 |
4 files changed, 134 insertions, 12 deletions
diff --git a/internal/handlers/handlers.go b/internal/handlers/handlers.go index f31fc56..ed100fc 100644 --- a/internal/handlers/handlers.go +++ b/internal/handlers/handlers.go @@ -55,8 +55,14 @@ func New(store *store.Store, todoist api.TodoistAPI, trello api.TrelloAPI, obsid func (h *Handler) HandleDashboard(w http.ResponseWriter, r *http.Request) { ctx := r.Context() + // Extract tab query parameter for state persistence + tab := r.URL.Query().Get("tab") + if tab == "" { + tab = "tasks" + } + // Aggregate data from all sources - data, err := h.aggregateData(ctx, false) + dashboardData, err := h.aggregateData(ctx, false) if err != nil { http.Error(w, "Failed to load dashboard data", http.StatusInternalServerError) log.Printf("Error aggregating data: %v", err) @@ -69,6 +75,15 @@ func (h *Handler) HandleDashboard(w http.ResponseWriter, r *http.Request) { return } + // Wrap dashboard data with active tab for template + data := struct { + *models.DashboardData + ActiveTab string + }{ + DashboardData: dashboardData, + ActiveTab: tab, + } + if err := h.templates.ExecuteTemplate(w, "index.html", data); err != nil { http.Error(w, "Failed to render template", http.StatusInternalServerError) log.Printf("Error rendering template: %v", err) diff --git a/internal/handlers/tab_state_test.go b/internal/handlers/tab_state_test.go new file mode 100644 index 0000000..d3f0fce --- /dev/null +++ b/internal/handlers/tab_state_test.go @@ -0,0 +1,106 @@ +package handlers + +import ( + "net/http" + "net/http/httptest" + "strings" + "testing" + + "task-dashboard/internal/api" + "task-dashboard/internal/config" + "task-dashboard/internal/store" +) + +func TestHandleDashboard_TabState(t *testing.T) { + // Create a temporary database for testing + db, err := store.New(":memory:") + if err != nil { + t.Fatalf("Failed to create test database: %v", err) + } + defer db.Close() + + // Create mock API clients + todoistClient := api.NewTodoistClient("test-key") + trelloClient := api.NewTrelloClient("test-key", "test-token") + + // Create test config + cfg := &config.Config{ + Port: "8080", + CacheTTLMinutes: 5, + } + + // Create handler + h := New(db, todoistClient, trelloClient, nil, nil, cfg) + + // Skip if templates are not loaded (test environment issue) + if h.templates == nil { + t.Skip("Templates not available in test environment") + } + + tests := []struct { + name string + url string + expectedTab string + expectedActive string + expectedHxGet string + }{ + { + name: "default to tasks tab", + url: "/", + expectedTab: "tasks", + expectedActive: `class="tab-button tab-button-active"`, + expectedHxGet: `hx-get="/tabs/tasks"`, + }, + { + name: "notes tab from query param", + url: "/?tab=notes", + expectedTab: "notes", + expectedActive: `class="tab-button tab-button-active"`, + expectedHxGet: `hx-get="/tabs/notes"`, + }, + { + name: "planning tab from query param", + url: "/?tab=planning", + expectedTab: "planning", + expectedActive: `class="tab-button tab-button-active"`, + expectedHxGet: `hx-get="/tabs/planning"`, + }, + { + name: "meals tab from query param", + url: "/?tab=meals", + expectedTab: "meals", + expectedActive: `class="tab-button tab-button-active"`, + expectedHxGet: `hx-get="/tabs/meals"`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + req := httptest.NewRequest(http.MethodGet, tt.url, nil) + w := httptest.NewRecorder() + + h.HandleDashboard(w, req) + + resp := w.Result() + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + t.Errorf("Expected status 200, got %d", resp.StatusCode) + return + } + + body := w.Body.String() + + // Check that the expected tab has the active class + // We need to find the button with the expected tab name and check if it has the active class + if !strings.Contains(body, tt.expectedActive) { + t.Errorf("Expected active class in response body") + } + + // Check that the content div loads the correct tab + if !strings.Contains(body, tt.expectedHxGet) { + t.Errorf("Expected hx-get=\"/tabs/%s\" in response body, got: %s", tt.expectedTab, body) + } + }) + } +} diff --git a/web/static/js/app.js b/web/static/js/app.js index a930b1f..2f2cb6f 100644 --- a/web/static/js/app.js +++ b/web/static/js/app.js @@ -3,8 +3,9 @@ // Constants const AUTO_REFRESH_INTERVAL = 5 * 60 * 1000; // 5 minutes in milliseconds -// Track current active tab -let currentTab = 'tasks'; +// Track current active tab (read from URL for state persistence) +const urlParams = new URLSearchParams(window.location.search); +let currentTab = urlParams.get('tab') || 'tasks'; let autoRefreshTimer = null; // Initialize on page load diff --git a/web/templates/index.html b/web/templates/index.html index b545a6e..a1210d6 100644 --- a/web/templates/index.html +++ b/web/templates/index.html @@ -26,34 +26,34 @@ <div class="border-b border-gray-200 mb-8 no-print"> <nav class="-mb-px flex space-x-8"> <button - class="tab-button tab-button-active" + class="tab-button {{if eq .ActiveTab "tasks"}}tab-button-active{{end}}" hx-get="/tabs/tasks" hx-target="#tab-content" - hx-push-url="false" + hx-push-url="?tab=tasks" onclick="setActiveTab(this)"> ✓ Tasks </button> <button - class="tab-button" + class="tab-button {{if eq .ActiveTab "planning"}}tab-button-active{{end}}" hx-get="/tabs/planning" hx-target="#tab-content" - hx-push-url="false" + hx-push-url="?tab=planning" onclick="setActiveTab(this)"> 📋 Planning </button> <button - class="tab-button" + class="tab-button {{if eq .ActiveTab "notes"}}tab-button-active{{end}}" hx-get="/tabs/notes" hx-target="#tab-content" - hx-push-url="false" + hx-push-url="?tab=notes" onclick="setActiveTab(this)"> 📝 Notes </button> <button - class="tab-button" + class="tab-button {{if eq .ActiveTab "meals"}}tab-button-active{{end}}" hx-get="/tabs/meals" hx-target="#tab-content" - hx-push-url="false" + hx-push-url="?tab=meals" onclick="setActiveTab(this)"> 🍽️ Meals </button> @@ -62,7 +62,7 @@ <!-- Tab Content --> <div id="tab-content" - hx-get="/tabs/tasks" + hx-get="/tabs/{{.ActiveTab}}" hx-trigger="load" hx-swap="innerHTML"> <div class="text-center py-8"> |
