From c7a16dbcaeaed5cff8f152e2b7bb261bd8ad3f27 Mon Sep 17 00:00:00 2001 From: Arthur Belleville Date: Sat, 16 May 2026 21:49:23 +0200 Subject: [PATCH] feat(15-03): wire AppLayout into all authenticated handlers and templates - TablosListHandler: derives sidebarTablos from cardViews, calls TablosDashboard with activePath="/" - TabloDetailHandler: fetches ListTablosByUser for sidebar, calls TabloDetailPage with activePath="" - TabloUpdateHandler: fetches ListTablosByUser for non-HTMX error path - renderTabloCreateError: derives errorSidebarTablos from errorCardViews - TabloDiscussionTabHandler, TabloEventsTabHandler, TabloFilesTabHandler, TabloTasksTabHandler: fetch ListTablosByUser for non-HTMX full-page renders - PlanningPageHandler: fetches ListTablosByUser, calls PlanningPage with activePath="/planning" - AccountProvidersHandler: fetches ListTablosByUser, calls AccountProvidersPage with activePath="/" - planning.templ: updated signature + switched to @AppLayout - account_providers.templ: updated signature + switched to @AppLayout --- backend/internal/web/handlers_account.go | 13 +++++++- backend/internal/web/handlers_discussion.go | 10 +++++- backend/internal/web/handlers_events.go | 10 +++++- backend/internal/web/handlers_files.go | 20 ++++++++++-- backend/internal/web/handlers_planning.go | 13 +++++++- backend/internal/web/handlers_tablos.go | 36 ++++++++++++++++++--- backend/templates/account_providers.templ | 9 ++++-- backend/templates/planning.templ | 5 +-- 8 files changed, 101 insertions(+), 15 deletions(-) diff --git a/backend/internal/web/handlers_account.go b/backend/internal/web/handlers_account.go index f3768f6..18b3c4c 100644 --- a/backend/internal/web/handlers_account.go +++ b/backend/internal/web/handlers_account.go @@ -1,9 +1,11 @@ package web import ( + "log/slog" "net/http" "backend/internal/auth" + "backend/internal/db/sqlc" "backend/templates" "github.com/gorilla/csrf" @@ -34,7 +36,16 @@ func AccountProvidersHandler(deps AuthDeps) http.HandlerFunc { } } + sidebarTablos, err := deps.Queries.ListTablosByUser(r.Context(), user.ID) + if err != nil { + slog.Default().Error("account providers: ListTablosByUser failed", "user_id", user.ID, "err", err) + sidebarTablos = []sqlc.Tablo{} + } + if sidebarTablos == nil { + sidebarTablos = []sqlc.Tablo{} + } + w.Header().Set("Content-Type", "text/html; charset=utf-8") - _ = templates.AccountProvidersPage(user, statuses, csrf.Token(r)).Render(r.Context(), w) + _ = templates.AccountProvidersPage(user, statuses, csrf.Token(r), "/", sidebarTablos).Render(r.Context(), w) } } diff --git a/backend/internal/web/handlers_discussion.go b/backend/internal/web/handlers_discussion.go index 5dc6aa3..7212a59 100644 --- a/backend/internal/web/handlers_discussion.go +++ b/backend/internal/web/handlers_discussion.go @@ -64,7 +64,15 @@ func TabloDiscussionTabHandler(deps DiscussionDeps) http.HandlerFunc { _ = templates.DiscussionTabFragment(tablo, data, templates.DiscussionForm{}, templates.DiscussionErrors{}, csrf.Token(r)).Render(r.Context(), w) return } - _ = templates.TabloDetailPage(user, csrf.Token(r), tablo, nil, nil, templates.EtapeTaskCounts{}, templates.EtapeFilter{}, nil, templates.EventsCalendar{}, data, "discussion").Render(r.Context(), w) + discussionSidebarTablos, sidebarErr := deps.Queries.ListTablosByUser(r.Context(), user.ID) + if sidebarErr != nil { + slog.Default().Error("discussion: ListTablosByUser failed", "user_id", user.ID, "err", sidebarErr) + discussionSidebarTablos = []sqlc.Tablo{} + } + if discussionSidebarTablos == nil { + discussionSidebarTablos = []sqlc.Tablo{} + } + _ = templates.TabloDetailPage(user, csrf.Token(r), "", discussionSidebarTablos, tablo, nil, nil, templates.EtapeTaskCounts{}, templates.EtapeFilter{}, nil, templates.EventsCalendar{}, data, "discussion").Render(r.Context(), w) } } diff --git a/backend/internal/web/handlers_events.go b/backend/internal/web/handlers_events.go index 04f8d0e..1da6780 100644 --- a/backend/internal/web/handlers_events.go +++ b/backend/internal/web/handlers_events.go @@ -166,7 +166,15 @@ func TabloEventsTabHandler(deps EventsDeps) http.HandlerFunc { _ = templates.EventsTabFragment(tablo, calendar, csrf.Token(r)).Render(r.Context(), w) return } - _ = templates.TabloDetailPage(user, csrf.Token(r), tablo, nil, nil, templates.EtapeTaskCounts{}, templates.EtapeFilter{}, nil, calendar, templates.DiscussionTabData{}, "events").Render(r.Context(), w) + eventsSidebarTablos, sidebarErr := deps.Queries.ListTablosByUser(r.Context(), user.ID) + if sidebarErr != nil { + slog.Default().Error("events: ListTablosByUser failed", "user_id", user.ID, "err", sidebarErr) + eventsSidebarTablos = []sqlc.Tablo{} + } + if eventsSidebarTablos == nil { + eventsSidebarTablos = []sqlc.Tablo{} + } + _ = templates.TabloDetailPage(user, csrf.Token(r), "", eventsSidebarTablos, tablo, nil, nil, templates.EtapeTaskCounts{}, templates.EtapeFilter{}, nil, calendar, templates.DiscussionTabData{}, "events").Render(r.Context(), w) } } diff --git a/backend/internal/web/handlers_files.go b/backend/internal/web/handlers_files.go index 08f2c78..0477597 100644 --- a/backend/internal/web/handlers_files.go +++ b/backend/internal/web/handlers_files.go @@ -95,7 +95,15 @@ func TabloFilesTabHandler(deps FilesDeps) http.HandlerFunc { _ = templates.FilesTabFragment(tablo, fileList, csrf.Token(r)).Render(r.Context(), w) return } - _ = templates.TabloDetailPage(user, csrf.Token(r), tablo, nil, nil, templates.EtapeTaskCounts{}, templates.EtapeFilter{}, fileList, templates.EventsCalendar{}, templates.DiscussionTabData{}, "files").Render(r.Context(), w) + filesSidebarTablos, sidebarErr := deps.Queries.ListTablosByUser(r.Context(), user.ID) + if sidebarErr != nil { + slog.Default().Error("files tab: ListTablosByUser failed", "user_id", user.ID, "err", sidebarErr) + filesSidebarTablos = []sqlc.Tablo{} + } + if filesSidebarTablos == nil { + filesSidebarTablos = []sqlc.Tablo{} + } + _ = templates.TabloDetailPage(user, csrf.Token(r), "", filesSidebarTablos, tablo, nil, nil, templates.EtapeTaskCounts{}, templates.EtapeFilter{}, fileList, templates.EventsCalendar{}, templates.DiscussionTabData{}, "files").Render(r.Context(), w) } } @@ -119,7 +127,15 @@ func TabloTasksTabHandler(deps FilesDeps) http.HandlerFunc { _ = templates.TasksTabFragment(tablo, tasks, etapes, counts, filter, csrf.Token(r)).Render(r.Context(), w) return } - _ = templates.TabloDetailPage(user, csrf.Token(r), tablo, tasks, etapes, counts, filter, nil, templates.EventsCalendar{}, templates.DiscussionTabData{}, "tasks").Render(r.Context(), w) + tasksSidebarTablos, sidebarErr := deps.Queries.ListTablosByUser(r.Context(), user.ID) + if sidebarErr != nil { + slog.Default().Error("tasks tab: ListTablosByUser failed", "user_id", user.ID, "err", sidebarErr) + tasksSidebarTablos = []sqlc.Tablo{} + } + if tasksSidebarTablos == nil { + tasksSidebarTablos = []sqlc.Tablo{} + } + _ = templates.TabloDetailPage(user, csrf.Token(r), "", tasksSidebarTablos, tablo, tasks, etapes, counts, filter, nil, templates.EventsCalendar{}, templates.DiscussionTabData{}, "tasks").Render(r.Context(), w) } } diff --git a/backend/internal/web/handlers_planning.go b/backend/internal/web/handlers_planning.go index 1b7d0c2..4340ce5 100644 --- a/backend/internal/web/handlers_planning.go +++ b/backend/internal/web/handlers_planning.go @@ -53,7 +53,18 @@ func PlanningPageHandler(deps PlanningDeps) http.HandlerFunc { } agenda := templates.NewPlanningAgenda(start, end, parsePlanningStart("", now()), rows) + + sidebarTablos, err := deps.Queries.ListTablosByUser(r.Context(), user.ID) + if err != nil { + slog.Default().Error("planning: ListTablosByUser failed", "user_id", user.ID, "err", err) + http.Error(w, "internal server error", http.StatusInternalServerError) + return + } + if sidebarTablos == nil { + sidebarTablos = []sqlc.Tablo{} + } + w.Header().Set("Content-Type", "text/html; charset=utf-8") - _ = templates.PlanningPage(user, csrf.Token(r), agenda).Render(r.Context(), w) + _ = templates.PlanningPage(user, csrf.Token(r), "/planning", sidebarTablos, agenda).Render(r.Context(), w) } } diff --git a/backend/internal/web/handlers_tablos.go b/backend/internal/web/handlers_tablos.go index d065186..2dbaad0 100644 --- a/backend/internal/web/handlers_tablos.go +++ b/backend/internal/web/handlers_tablos.go @@ -50,8 +50,14 @@ func TablosListHandler(deps TablosDeps) http.HandlerFunc { tabloRows = []sqlc.ListTablosByUserWithDiscussionUnreadRow{} } + cardViews := templates.TabloCardsFromUnreadRows(tabloRows) + sidebarTablos := make([]sqlc.Tablo, 0, len(cardViews)) + for _, cv := range cardViews { + sidebarTablos = append(sidebarTablos, cv.Tablo) + } + w.Header().Set("Content-Type", "text/html; charset=utf-8") - _ = templates.TablosDashboard(user, csrf.Token(r), templates.TabloCardsFromUnreadRows(tabloRows)).Render(r.Context(), w) + _ = templates.TablosDashboard(user, csrf.Token(r), "/", sidebarTablos, cardViews).Render(r.Context(), w) } } @@ -201,8 +207,18 @@ func TabloDetailHandler(deps TablosDeps) http.HandlerFunc { if tasks == nil { tasks = []sqlc.Task{} } + // Fetch sidebar tablos for AppLayout. On error, log and use empty slice — + // the page still renders, just without a populated sidebar projects list. + sidebarTablos, err := deps.Queries.ListTablosByUser(r.Context(), user.ID) + if err != nil { + slog.Default().Error("tablos detail: ListTablosByUser failed", "user_id", user.ID, "err", err) + sidebarTablos = []sqlc.Tablo{} + } + if sidebarTablos == nil { + sidebarTablos = []sqlc.Tablo{} + } w.Header().Set("Content-Type", "text/html; charset=utf-8") - _ = templates.TabloDetailPage(user, csrf.Token(r), tablo, tasks, nil, templates.EtapeTaskCounts{}, templates.EtapeFilter{}, nil, templates.EventsCalendar{}, templates.DiscussionTabData{}, "overview").Render(r.Context(), w) + _ = templates.TabloDetailPage(user, csrf.Token(r), "", sidebarTablos, tablo, tasks, nil, templates.EtapeTaskCounts{}, templates.EtapeFilter{}, nil, templates.EventsCalendar{}, templates.DiscussionTabData{}, "overview").Render(r.Context(), w) } } @@ -308,7 +324,14 @@ func TabloUpdateHandler(deps TablosDeps) http.HandlerFunc { if tasks == nil { tasks = []sqlc.Task{} } - _ = templates.TabloDetailPage(user, csrf.Token(r), tablo, tasks, nil, templates.EtapeTaskCounts{}, templates.EtapeFilter{}, nil, templates.EventsCalendar{}, templates.DiscussionTabData{}, "overview").Render(ctx, w) + updateSidebarTablos, sidebarErr := deps.Queries.ListTablosByUser(ctx, user.ID) + if sidebarErr != nil { + updateSidebarTablos = []sqlc.Tablo{} + } + if updateSidebarTablos == nil { + updateSidebarTablos = []sqlc.Tablo{} + } + _ = templates.TabloDetailPage(user, csrf.Token(r), "", updateSidebarTablos, tablo, tasks, nil, templates.EtapeTaskCounts{}, templates.EtapeFilter{}, nil, templates.EventsCalendar{}, templates.DiscussionTabData{}, "overview").Render(ctx, w) return } @@ -430,5 +453,10 @@ func renderTabloCreateError(w http.ResponseWriter, r *http.Request, form templat // Render full page — form fragment is not embedded in the full page by default; // for the non-HTMX error case we redirect so the user sees their list intact // and can try again (simpler than threading form state through the full page). - _ = templates.TablosDashboard(user, csrf.Token(r), templates.TabloCardsFromUnreadRows(tabloRows)).Render(r.Context(), w) + errorCardViews := templates.TabloCardsFromUnreadRows(tabloRows) + errorSidebarTablos := make([]sqlc.Tablo, 0, len(errorCardViews)) + for _, cv := range errorCardViews { + errorSidebarTablos = append(errorSidebarTablos, cv.Tablo) + } + _ = templates.TablosDashboard(user, csrf.Token(r), "/", errorSidebarTablos, errorCardViews).Render(r.Context(), w) } diff --git a/backend/templates/account_providers.templ b/backend/templates/account_providers.templ index 2f26128..3198dd7 100644 --- a/backend/templates/account_providers.templ +++ b/backend/templates/account_providers.templ @@ -1,9 +1,12 @@ package templates -import "backend/internal/auth" +import ( + "backend/internal/auth" + "backend/internal/db/sqlc" +) -templ AccountProvidersPage(user *auth.User, providers []LinkedProviderStatus, csrfToken string) { - @Layout("Linked providers", user, csrfToken) { +templ AccountProvidersPage(user *auth.User, providers []LinkedProviderStatus, csrfToken string, activePath string, tablos []sqlc.Tablo) { + @AppLayout("Linked providers", user, csrfToken, activePath, tablos) {

Linked providers

diff --git a/backend/templates/planning.templ b/backend/templates/planning.templ index 2e0456f..6ec8d8a 100644 --- a/backend/templates/planning.templ +++ b/backend/templates/planning.templ @@ -2,10 +2,11 @@ package templates import ( "backend/internal/auth" + "backend/internal/db/sqlc" ) -templ PlanningPage(user *auth.User, csrfToken string, agenda PlanningAgenda) { - @Layout("Planning - Xtablo", user, csrfToken) { +templ PlanningPage(user *auth.User, csrfToken string, activePath string, tablos []sqlc.Tablo, agenda PlanningAgenda) { + @AppLayout("Planning - Xtablo", user, csrfToken, activePath, tablos) {