Compare commits

..

1 Commits

Author SHA1 Message Date
agent-company f5734fea10 feat: add org filter dropdown to Dashboard view
Add an org filter select to the dashboard that allows users to narrow
the triage queue to a specific organization. The filter uses HTMX to
update the view without a full page reload, mirroring the pattern
already used in the issues and pulls views.

Closes leeworks-agents/gitea-mobile#68

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 04:11:55 +00:00
3 changed files with 29 additions and 65 deletions
+17 -47
View File
@@ -39,7 +39,6 @@ func (h *Handler) RegisterRoutes(mux *http.ServeMux) {
// Issues.
mux.HandleFunc("GET /issues", h.ListIssues)
mux.HandleFunc("GET /issues/new", h.NewIssue)
mux.HandleFunc("GET /issues/new/labels", h.NewIssueLabels)
mux.HandleFunc("POST /issues", h.CreateIssue)
mux.HandleFunc("POST /issues/{owner}/{repo}/{index}/labels", h.ApplyLabels)
mux.HandleFunc("POST /issues/{owner}/{repo}/{index}/close", h.CloseIssue)
@@ -189,18 +188,30 @@ func (h *Handler) Dashboard(w http.ResponseWriter, r *http.Request) {
token := getToken(r)
orgs := h.getUserOrgs(r)
selectedOrg := r.URL.Query().Get("org")
type dashboardData struct {
Items []giteaclient.TriageItem
Error string
Items []giteaclient.TriageItem
Orgs []string
SelectedOrg string
Error string
}
var data dashboardData
data := dashboardData{
Orgs: orgs,
SelectedOrg: selectedOrg,
}
if len(orgs) == 0 {
data.Error = "No organizations found. Check your token permissions."
} else {
queue, err := h.Client.GetTriageQueue(r.Context(), token, orgs)
// Determine which orgs to query.
queryOrgs := orgs
if selectedOrg != "" {
queryOrgs = []string{selectedOrg}
}
queue, err := h.Client.GetTriageQueue(r.Context(), token, queryOrgs)
if err != nil {
slog.Error("failed to get triage queue", "error", err)
data.Error = "Error loading triage queue."
@@ -568,38 +579,6 @@ func (h *Handler) NewIssue(w http.ResponseWriter, r *http.Request) {
renderPage(w, r, "New Issue", "issues", buf.String())
}
// NewIssueLabels handles GET /issues/new/labels — returns label checkboxes for a repo.
func (h *Handler) NewIssueLabels(w http.ResponseWriter, r *http.Request) {
token := getToken(r)
owner := r.URL.Query().Get("owner")
repo := r.URL.Query().Get("repo")
if owner == "" || repo == "" {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
fmt.Fprint(w, `<span class="empty">Select a repository first.</span>`)
return
}
labels, err := h.Client.GetRepoLabels(r.Context(), token, owner, repo)
if err != nil {
slog.Error("failed to fetch labels", "error", err, "owner", owner, "repo", repo)
w.Header().Set("Content-Type", "text/html; charset=utf-8")
fmt.Fprint(w, `<span class="empty">Error loading labels.</span>`)
return
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
if len(labels) == 0 {
fmt.Fprint(w, `<span class="empty">No labels available for this repository.</span>`)
return
}
for _, l := range labels {
fmt.Fprintf(w, `<label style="display:inline-block;margin:0.25rem 0.5rem 0.25rem 0;cursor:pointer;"><input type="checkbox" name="label_ids" value="%d" style="margin-right:0.25rem;"> <span class="label" style="color:#%s;border:1px solid #%s">%s</span></label>`,
l.ID, template.HTMLEscapeString(l.Color), template.HTMLEscapeString(l.Color), template.HTMLEscapeString(l.Name))
}
}
// CreateIssue handles POST /issues.
func (h *Handler) CreateIssue(w http.ResponseWriter, r *http.Request) {
token := getToken(r)
@@ -613,15 +592,6 @@ func (h *Handler) CreateIssue(w http.ResponseWriter, r *http.Request) {
title := r.FormValue("title")
body := r.FormValue("body")
// Parse label IDs from form checkboxes.
var labelIDs []int64
for _, idStr := range r.Form["label_ids"] {
id, err := strconv.ParseInt(idStr, 10, 64)
if err == nil {
labelIDs = append(labelIDs, id)
}
}
if owner == "" || repo == "" || title == "" {
if isHTMX(r) {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
@@ -633,7 +603,7 @@ func (h *Handler) CreateIssue(w http.ResponseWriter, r *http.Request) {
return
}
issue, err := h.Client.CreateIssue(r.Context(), token, owner, repo, title, body, labelIDs)
issue, err := h.Client.CreateIssue(r.Context(), token, owner, repo, title, body, nil)
if err != nil {
slog.Error("failed to create issue", "error", err)
if isHTMX(r) {
+1 -18
View File
@@ -20,11 +20,6 @@
<input type="hidden" name="repo" id="repo-input">
</div>
<div class="form-group" id="label-section" style="display:none;">
<label>Labels</label>
<div id="label-list"></div>
</div>
<div class="form-group">
<label for="title">Title</label>
<input type="text" id="title" name="title" placeholder="Issue title" required>
@@ -57,19 +52,7 @@
}
}
repoSelect.addEventListener('change', function() {
splitOwnerRepo();
var labelSection = document.getElementById('label-section');
var labelList = document.getElementById('label-list');
if (ownerInput.value && repoInput.value) {
labelList.innerHTML = '<span class="empty">Loading labels...</span>';
labelSection.style.display = 'block';
htmx.ajax('GET', '/issues/new/labels?owner=' + encodeURIComponent(ownerInput.value) + '&repo=' + encodeURIComponent(repoInput.value), {target: '#label-list', swap: 'innerHTML'});
} else {
labelSection.style.display = 'none';
labelList.innerHTML = '';
}
});
repoSelect.addEventListener('change', splitOwnerRepo);
// Validate before HTMX submit.
document.getElementById('create-issue-form').addEventListener('htmx:configRequest', function(evt) {
+11
View File
@@ -1,6 +1,17 @@
{{define "content"}}
<h1>Dashboard</h1>
{{if gt (len .Orgs) 1}}
<div class="filter-bar">
<select name="org" hx-get="/" hx-trigger="change" hx-target="#main-content" hx-swap="innerHTML" hx-push-url="true">
<option value="">All orgs</option>
{{range .Orgs}}
<option value="{{.}}" {{if eq . $.SelectedOrg}}selected{{end}}>{{.}}</option>
{{end}}
</select>
</div>
{{end}}
{{if .Error}}
<p class="empty">{{.Error}}</p>
{{else if not .Items}}