unify components and layout

This commit is contained in:
Robin Olsen
2026-02-24 15:13:31 +01:00
parent dd92c5741a
commit 38ae7905c1
24 changed files with 802 additions and 963 deletions

View File

@@ -2,94 +2,57 @@ package handler
import (
"net/http"
"sort"
"strings"
"github.com/LazyBachelor/LazyPM/internal/models"
"github.com/LazyBachelor/LazyPM/pkg/web/components"
"github.com/LazyBachelor/LazyPM/pkg/web/routes"
)
func orDisplay(s, fallback string) string {
if s != "" {
return s
}
return fallback
}
func selectedID(issue *models.Issue) string {
if issue == nil {
return ""
}
return issue.ID
}
func DashboardHandler(w http.ResponseWriter, r *http.Request) {
app := App(r)
hx := HTMX(r)
issues, err := app.Issues.AllIssues(r.Context())
query := strings.TrimSpace(r.URL.Query().Get("q"))
filter := models.IssueFilter{Limit: 100}
issues, err := app.Issues.SearchIssues(r.Context(), query, filter)
if err != nil {
http.Error(w, "Failed to retrieve issues", http.StatusInternalServerError)
return
}
var activeIssues, closedIssues []models.Issue
for _, issue := range issues {
if issue.Status == models.StatusClosed {
closedIssues = append(closedIssues, issue)
} else {
activeIssues = append(activeIssues, issue)
}
}
sort.Slice(activeIssues, func(i, j int) bool { return activeIssues[i].Priority > activeIssues[j].Priority })
sort.Slice(closedIssues, func(i, j int) bool { return closedIssues[i].Priority > closedIssues[j].Priority })
selID := r.URL.Query().Get("id")
var selectedIssue *models.Issue
if selID != "" {
for i := range issues {
if issues[i].ID == selID {
selectedIssue = &issues[i]
if len(issues) > 0 {
selectedIssue = issues[0]
}
selectedIssueID := r.URL.Query().Get("selected-issue")
if selectedIssueID != "" {
for _, issue := range issues {
if issue.ID == selectedIssueID {
selectedIssue = issue
break
}
}
}
if selectedIssue == nil && len(activeIssues) > 0 {
selectedIssue = &activeIssues[0]
selID = selectedIssue.ID
}
searchQuery := r.URL.Query().Get("q")
var displayOwner, displayAssignee string
if selectedIssue != nil {
displayOwner = orDisplay(selectedIssue.Owner, "—")
displayAssignee = orDisplay(selectedIssue.Assignee, "—")
isPartial := r.URL.Query().Get("partial") == "true"
if hx.IsHxRequest() && isPartial {
routes.DashboardIssueList(issues, selectedIssueID).Render(r.Context(), w)
return
}
props := routes.DashboardProps{
ActiveIssues: activeIssues,
ClosedIssues: closedIssues,
SelectedIssue: selectedIssue,
SelectedID: selectedID(selectedIssue),
SearchQuery: searchQuery,
DisplayOwner: displayOwner,
DisplayAssignee: displayAssignee,
BaseURL: "/",
QueryParam: query,
Issues: issues,
SelectedIssue: selectedIssue,
EmptyText: "No issues found",
}
if HTMX(r).IsHxRequest() {
components.DashboardPage(components.DashboardPageProps{
ActiveIssues: props.ActiveIssues,
ClosedIssues: props.ClosedIssues,
SelectedIssue: props.SelectedIssue,
SelectedID: props.SelectedID,
BaseURL: "/dashboard",
QueryParam: "?id=",
EmptyText: "Select an issue to view details",
DisplayOwner: props.DisplayOwner,
DisplayAssignee: props.DisplayAssignee,
}).Render(r.Context(), w)
if hx.IsHxRequest() {
routes.DashboardContent(props).Render(r.Context(), w)
return
}
routes.Dashboard(props).Render(r.Context(), w)
}
}

View File

@@ -41,7 +41,7 @@ func CreateIssue(w http.ResponseWriter, r *http.Request) {
if err := ValidateForm(form); err != nil {
if hx.IsHxRequest() {
hx.WriteString("<div>Please fix the form errors</div>")
hx.WriteString("<div class=\"alert alert-error\">Please fix the form errors: " + err.Error() + "</div>")
} else {
http.Error(w, "Validation error: "+err.Error(), http.StatusUnprocessableEntity)
}
@@ -55,7 +55,15 @@ func CreateIssue(w http.ResponseWriter, r *http.Request) {
}
if hx.IsHxRequest() {
hx.WriteString("<div>Issue created successfully</div>")
// Success: close modal by triggering event, and refresh issue list
w.Header().Set("HX-Trigger", `{"closeModal": "create-issue-modal", "issueCreated": "`+issue.ID+`"}`)
w.Header().Set("HX-Retarget", "#issue-list-container")
w.Header().Set("HX-Reswap", "innerHTML")
// Get updated issues list
filter := models.IssueFilter{Limit: 100}
issues, _ := app.Issues.SearchIssues(r.Context(), "", filter)
routes.DashboardIssueList(issues, "").Render(r.Context(), w)
return
}
@@ -110,25 +118,18 @@ func GetIssue(w http.ResponseWriter, r *http.Request) {
comments := r.Context().Value(commentsKey).([]*models.Comment)
hx := HTMX(r)
displayOwner := orDisplay(issue.Owner, "—")
displayAssignee := orDisplay(issue.Assignee, "—")
if !hx.IsHxRequest() && strings.Contains(r.Header.Get("Accept"), "text/html") {
routes.IssueDetail(routes.IssueDetailProps{
Issue: *issue,
DisplayOwner: displayOwner,
DisplayAssignee: displayAssignee,
Comments: comments,
Issue: issue,
Comments: comments,
}).Render(r.Context(), w)
return
}
if hx.IsHxRequest() {
routes.IssueDetailContent(routes.IssueDetailProps{
Issue: *issue,
DisplayOwner: displayOwner,
DisplayAssignee: displayAssignee,
Comments: comments,
Issue: issue,
Comments: comments,
}).Render(r.Context(), w)
return
}