go-htmx-gsd #1
8 changed files with 101 additions and 15 deletions
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
<section class="mx-auto max-w-xl">
|
||||
<h1 class="mb-6 text-xl font-semibold">Linked providers</h1>
|
||||
<div class="space-y-2">
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
<div class="space-y-6">
|
||||
<div class="flex flex-wrap items-start justify-between gap-4">
|
||||
<div>
|
||||
|
|
|
|||
Loading…
Reference in a new issue