- Create tablo_detail.templ with 8 templ components: TabloDetailPage, TabloDetailHeader, TabloDetailTabBar, TabloDetailKanbanBoard, TabloDetailKanbanColumn, TabloDetailTaskCard, TabloDetailEtapesSection, TabloDetailSortableScript
- Tab links use hx-get + hx-target="#tab-content" + hx-push-url="true" per UI-SPEC interaction contract
- Each kanban column has hidden reorder form id="reorder-form-{status}" for Sortable.js onEnd
- Create tablo_detail_tab.go with GetTabloDetailTab handler for HTMX tab content swaps
- Tasks tab returns kanban board fragment; other tabs return "coming soon" placeholder
76 lines
2.2 KiB
Go
76 lines
2.2 KiB
Go
package handlers
|
|
|
|
import (
|
|
"net/http"
|
|
|
|
"github.com/google/uuid"
|
|
"xtablo-backend/internal/web/views"
|
|
)
|
|
|
|
// GetTabloDetailTab handles GET /tablos/{tabloID}/{tab} for HTMX tab content swaps.
|
|
func (h *AuthHandler) GetTabloDetailTab() http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
user, ok := h.authenticatedUser(r.Context(), r)
|
|
if !ok {
|
|
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
|
return
|
|
}
|
|
|
|
tabloID, err := uuid.Parse(r.PathValue("tabloID"))
|
|
if err != nil {
|
|
http.Error(w, "invalid tablo id", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
tab := r.PathValue("tab")
|
|
|
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
|
|
|
switch tab {
|
|
case "tasks":
|
|
// Fetch tablo + tasks, build full view model, return kanban board fragment
|
|
tablos, err := h.repo.ListTablos(r.Context(), ListTablosInput{OwnerID: user.ID})
|
|
if err != nil {
|
|
http.Error(w, "failed to load tablos", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
tablo, ok := findTabloByID(tablos, tabloID)
|
|
if !ok {
|
|
http.Error(w, "tablo not found", http.StatusNotFound)
|
|
return
|
|
}
|
|
|
|
taskRepo, ok := h.repo.(tabloDetailRepository)
|
|
if !ok {
|
|
http.Error(w, "tasks repository not configured", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
tasks, err := taskRepo.ListTasksByTablo(r.Context(), ListTasksByTabloInput{
|
|
OwnerID: user.ID,
|
|
TabloID: tabloID,
|
|
})
|
|
if err != nil {
|
|
http.Error(w, "failed to load tasks", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
ownerName := ""
|
|
if owner, err := h.repo.GetPublicUserByID(r.Context(), user.ID); err == nil {
|
|
ownerName = owner.DisplayName
|
|
}
|
|
|
|
vm := views.NewTabloDetailViewModel(tablo, tasks, ownerName)
|
|
if renderErr := views.TabloDetailKanbanBoard(vm.Columns, tabloID.String()).Render(r.Context(), w); renderErr != nil {
|
|
http.Error(w, "failed to render tab", http.StatusInternalServerError)
|
|
}
|
|
|
|
default:
|
|
// Other tabs: overview, files, discussion, events — coming soon fragment
|
|
if _, writeErr := w.Write([]byte(`<div class="tab-coming-soon">Cette section arrive bientôt.</div>`)); writeErr != nil {
|
|
http.Error(w, "failed to render tab", http.StatusInternalServerError)
|
|
}
|
|
}
|
|
}
|
|
}
|