diff --git a/internal/handlers/handlers.go b/internal/handlers/handlers.go index db4a16b..b67ad6b 100644 --- a/internal/handlers/handlers.go +++ b/internal/handlers/handlers.go @@ -182,158 +182,155 @@ func (h *Handler) Dashboard(w http.ResponseWriter, r *http.Request) { token := getToken(r) orgs := h.getUserOrgs(r) + type dashboardData struct { + Items []giteaclient.TriageItem + Error string + } + + var data dashboardData + if len(orgs) == 0 { - renderPage(w, r, "Dashboard", "dashboard", - `

Dashboard

No organizations found. Check your token permissions.

`) - return + data.Error = "No organizations found. Check your token permissions." + } else { + queue, err := h.Client.GetTriageQueue(r.Context(), token, orgs) + if err != nil { + slog.Error("failed to get triage queue", "error", err) + data.Error = "Error loading triage queue." + } else { + data.Items = queue + } } - queue, err := h.Client.GetTriageQueue(r.Context(), token, orgs) + tmpl, err := template.ParseFiles("internal/templates/dashboard.html") if err != nil { - slog.Error("failed to get triage queue", "error", err) - renderPage(w, r, "Dashboard", "dashboard", - `

Dashboard

Error loading triage queue.

`) + slog.Error("failed to parse dashboard template", "error", err) + http.Error(w, "template error", http.StatusInternalServerError) return } - if len(queue) == 0 { - renderPage(w, r, "Dashboard", "dashboard", - `

Dashboard

No items need attention. Nice work!

`) + var buf strings.Builder + if err := tmpl.ExecuteTemplate(&buf, "content", data); err != nil { + slog.Error("failed to execute dashboard template", "error", err) + http.Error(w, "template error", http.StatusInternalServerError) return } - content := `

Dashboard

` - for _, item := range queue { - typeBadge := `issue` - if item.Type == "pull" { - typeBadge = `PR` - } - - labels := "" - for _, l := range item.Labels { - color := "#8b949e" - switch l { - case "P1": - color = "#f85149" - case "P2": - color = "#d29922" - case "P3": - color = "#58a6ff" - } - labels += fmt.Sprintf(`%s`, color, color, template.HTMLEscapeString(l)) - } - - content += fmt.Sprintf(`
-
%s %s
-
%s/%s #%d %s
-
`, typeBadge, template.HTMLEscapeString(item.Title), - template.HTMLEscapeString(item.RepoOwner), - template.HTMLEscapeString(item.RepoName), - item.Number, labels) - } - - renderPage(w, r, "Dashboard", "dashboard", content) + renderPage(w, r, "Dashboard", "dashboard", buf.String()) } // ListIssues handles GET /issues. func (h *Handler) ListIssues(w http.ResponseWriter, r *http.Request) { token := getToken(r) - orgs := h.getUserOrgs(r) + orgNames := h.getUserOrgs(r) - if len(orgs) == 0 { - renderPage(w, r, "Issues", "issues", - `

Issues

No organizations found.

`) - return + type issuesData struct { + Issues []giteaclient.Issue + Orgs []string + SelectedOrg string + SelectedState string + HasMore bool + NextPage int + Error string } - issues, err := h.Client.ListAllIssues(r.Context(), token, orgs) + selectedOrg := r.URL.Query().Get("org") + selectedState := r.URL.Query().Get("state") + if selectedState == "" { + selectedState = "open" + } + + data := issuesData{ + Orgs: orgNames, + SelectedOrg: selectedOrg, + SelectedState: selectedState, + } + + if len(orgNames) == 0 { + data.Error = "No organizations found." + } else { + // Filter to selected org if specified. + queryOrgs := orgNames + if selectedOrg != "" { + queryOrgs = []string{selectedOrg} + } + + issues, err := h.Client.ListAllIssues(r.Context(), token, queryOrgs) + if err != nil { + slog.Error("failed to list issues", "error", err) + data.Error = "Error loading issues." + } else { + data.Issues = issues + } + } + + tmpl, err := template.ParseFiles("internal/templates/issues.html") if err != nil { - slog.Error("failed to list issues", "error", err) - renderPage(w, r, "Issues", "issues", - `

Issues

Error loading issues.

`) + slog.Error("failed to parse issues template", "error", err) + http.Error(w, "template error", http.StatusInternalServerError) return } - if len(issues) == 0 { - renderPage(w, r, "Issues", "issues", - `

Issues

No open issues found.

`) + var buf strings.Builder + if err := tmpl.ExecuteTemplate(&buf, "content", data); err != nil { + slog.Error("failed to execute issues template", "error", err) + http.Error(w, "template error", http.StatusInternalServerError) return } - content := `

Issues

` - for _, issue := range issues { - labels := "" - for _, l := range issue.Labels { - labels += fmt.Sprintf(`%s`, - l.Color, l.Color, template.HTMLEscapeString(l.Name)) - } - - assignee := "" - if issue.Assignee != nil { - assignee = fmt.Sprintf(` · %s`, template.HTMLEscapeString(issue.Assignee.Login)) - } - - content += fmt.Sprintf(`
-
%s
-
%s/%s #%d %s%s
-
`, template.HTMLEscapeString(issue.Title), - template.HTMLEscapeString(issue.RepoOwner), - template.HTMLEscapeString(issue.RepoName), - issue.Number, labels, assignee) - } - - renderPage(w, r, "Issues", "issues", content) + renderPage(w, r, "Issues", "issues", buf.String()) } // ListPulls handles GET /pulls. func (h *Handler) ListPulls(w http.ResponseWriter, r *http.Request) { token := getToken(r) - orgs := h.getUserOrgs(r) + orgNames := h.getUserOrgs(r) - if len(orgs) == 0 { - renderPage(w, r, "Pull Requests", "pulls", - `

Pull Requests

No organizations found.

`) - return + type pullsData struct { + Pulls []giteaclient.PullRequest + Orgs []string + SelectedOrg string + Error string } - prs, err := h.Client.ListAllPullRequests(r.Context(), token, orgs) + selectedOrg := r.URL.Query().Get("org") + + data := pullsData{ + Orgs: orgNames, + SelectedOrg: selectedOrg, + } + + if len(orgNames) == 0 { + data.Error = "No organizations found." + } else { + queryOrgs := orgNames + if selectedOrg != "" { + queryOrgs = []string{selectedOrg} + } + + prs, err := h.Client.ListAllPullRequests(r.Context(), token, queryOrgs) + if err != nil { + slog.Error("failed to list pull requests", "error", err) + data.Error = "Error loading pull requests." + } else { + data.Pulls = prs + } + } + + tmpl, err := template.ParseFiles("internal/templates/pulls.html") if err != nil { - slog.Error("failed to list pull requests", "error", err) - renderPage(w, r, "Pull Requests", "pulls", - `

Pull Requests

Error loading pull requests.

`) + slog.Error("failed to parse pulls template", "error", err) + http.Error(w, "template error", http.StatusInternalServerError) return } - if len(prs) == 0 { - renderPage(w, r, "Pull Requests", "pulls", - `

Pull Requests

No open pull requests found.

`) + var buf strings.Builder + if err := tmpl.ExecuteTemplate(&buf, "content", data); err != nil { + slog.Error("failed to execute pulls template", "error", err) + http.Error(w, "template error", http.StatusInternalServerError) return } - content := `

Pull Requests

` - for _, pr := range prs { - labels := "" - for _, l := range pr.Labels { - labels += fmt.Sprintf(`%s`, - l.Color, l.Color, template.HTMLEscapeString(l.Name)) - } - - stats := fmt.Sprintf(`+%d -%d`, pr.Additions, pr.Deletions) - mergeStatus := "" - if pr.Mergeable { - mergeStatus = `mergeable` - } - - content += fmt.Sprintf(`
-
PR %s
-
%s/%s #%d %s %s %s
-
`, template.HTMLEscapeString(pr.Title), - template.HTMLEscapeString(pr.RepoOwner), - template.HTMLEscapeString(pr.RepoName), - pr.Number, labels, stats, mergeStatus) - } - - renderPage(w, r, "Pull Requests", "pulls", content) + renderPage(w, r, "Pull Requests", "pulls", buf.String()) } // IssueDetail handles GET /issues/{owner}/{repo}/{index}.