diff --git a/cmd/pm/tasks/backlogRefinement.go b/cmd/pm/tasks/backlogRefinement.go
index be536b5..5e7e150 100644
--- a/cmd/pm/tasks/backlogRefinement.go
+++ b/cmd/pm/tasks/backlogRefinement.go
@@ -2,6 +2,7 @@ package tasks
import (
"context"
+ "strings"
"github.com/LazyBachelor/LazyPM/internal/models"
"github.com/LazyBachelor/LazyPM/internal/utils/check"
@@ -12,12 +13,12 @@ const backlogRefinementDescription = `You are tasked with backlog refinement.
The product backlog has become cluttered with old and unclear issues. You need to groom the backlog:
-1. Review all issues in the backlog
-2. Identify stale or obsolete issues (older items that are no longer relevant)
-3. Update issue descriptions for clarity where needed
-4. Close issues that are duplicates or no longer applicable
-5. Reprioritize issues based on current business value
-6. Ensure remaining issues are well-defined and actionable
+1. Go to the backlog.
+2. Find two issues that got the same name or describe the same problem.
+3. Open one of these issues.
+4. Select "Close issue"
+5. Choose "Duplicate issue" as closing reason.
+6. Save/close issue.
Focus on making the backlog a reliable source of upcoming work.`
@@ -47,60 +48,64 @@ func (t *BacklogRefinementTask) Questions(interfaceType InterfaceType) Questions
return BaseQuestions(interfaceType).With(
huh.NewGroup(
huh.NewSelect[int]().
- Title("How many issues did you close or update during refinement?").
+ Title("How many duplicate issues did you close during refinement?").
Options(
- huh.NewOption("1-2", 1),
- huh.NewOption("3-4", 2),
- huh.NewOption("5+", 3),
+ huh.NewOption("1", 1),
+ huh.NewOption("2", 2),
+ huh.NewOption("3+", 3),
),
),
)
}
+func (t *BacklogRefinementTask) QuestionnaireKeys(_ InterfaceType) []string {
+ return []string{"task_completed", "task_difficulty"}
+}
+
func (t *BacklogRefinementTask) Setup(ctx context.Context) error {
if err := ClearIssues(t.app); err != nil {
return err
}
refinementIssues := []*models.Issue{
- NewIssueBuilder().
- WithTitle("Old feature request: Fax integration").
- WithDescription("Allow sending reports via fax. DEPRECATED - nobody uses fax anymore").
- WithPriority(3).
- WithStatus(models.StatusOpen).
- WithIssueType(models.TypeTask).
- Build(),
NewIssueBuilder().
WithTitle("User profile page").
- WithDescription("Create page for users to view profile. DUPLICATE of user-management epic").
+ WithDescription("Create page for users to view and edit their profile").
WithPriority(2).
WithStatus(models.StatusOpen).
WithIssueType(models.TypeTask).
Build(),
NewIssueBuilder().
- WithTitle("Mobile app redesign").
- WithDescription("Redesign mobile interface with modern UI patterns. Still relevant, needs clarity").
+ WithTitle("User profile page").
+ WithDescription("Allow users to view their profile information").
+ WithPriority(2).
+ WithStatus(models.StatusOpen).
+ WithIssueType(models.TypeTask).
+ Build(),
+ NewIssueBuilder().
+ WithTitle("Fix login timeout").
+ WithDescription("Login sometimes times out after 30 seconds").
WithPriority(1).
WithStatus(models.StatusOpen).
- WithIssueType(models.TypeTask).
+ WithIssueType(models.TypeBug).
Build(),
NewIssueBuilder().
- WithTitle("Legacy data export tool").
- WithDescription("Tool for exporting data in old format. OBSOLETE - format no longer supported").
- WithPriority(3).
+ WithTitle("Fix login timeout").
+ WithDescription("Users report login requests timing out").
+ WithPriority(1).
WithStatus(models.StatusOpen).
- WithIssueType(models.TypeTask).
+ WithIssueType(models.TypeBug).
Build(),
NewIssueBuilder().
- WithTitle("API v1 documentation").
- WithDescription("Document old API version. DEPRECATED - migrating to v2").
- WithPriority(3).
+ WithTitle("Mobile app redesign").
+ WithDescription("Redesign mobile interface with modern UI patterns").
+ WithPriority(2).
WithStatus(models.StatusOpen).
WithIssueType(models.TypeTask).
Build(),
NewIssueBuilder().
WithTitle("Customer feedback system").
- WithDescription("Build system for collecting user feedback. HIGH VALUE - prioritize").
+ WithDescription("Build system for collecting user feedback").
WithPriority(2).
WithStatus(models.StatusOpen).
WithIssueType(models.TypeTask).
@@ -122,5 +127,22 @@ func (t *BacklogRefinementTask) Setup(ctx context.Context) error {
func (t *BacklogRefinementTask) Validate(ctx context.Context) ValidationFeedback {
expect := check.NewExpector()
+ issues, err := FetchIssues(ctx, t.app, t.setupIssue)
+ if err != nil {
+ return expect.ValidationFeedback
+ }
+
+ var closedDuplicate *models.Issue
+ for _, issue := range issues {
+ if issue.Status == models.StatusClosed &&
+ strings.Contains(strings.ToLower(issue.CloseReason), "duplicate") {
+ closedDuplicate = issue
+ break
+ }
+ }
+
+ expect.Assert(closedDuplicate != nil,
+ "Expected one duplicate issue to be closed with 'Duplicate issue' as closing reason")
+
return expect.Complete()
}
diff --git a/internal/commands/issues/close.go b/internal/commands/issues/close.go
index 3c89b10..7af4b79 100644
--- a/internal/commands/issues/close.go
+++ b/internal/commands/issues/close.go
@@ -7,8 +7,6 @@ import (
"github.com/spf13/cobra"
)
-// CloseCmd represents the close command,
-// which allows users to close an existing issue by its ID.
var CloseCmd = &cobra.Command{
Use: "close [id]",
Short: "Close an existing issue",
@@ -21,8 +19,6 @@ var CloseCmd = &cobra.Command{
ValidArgsFunction: completeIssues,
}
-// runCloseCmd executes the close command logic,
-// which closes an issue by its ID after confirming with the user.
func runCloseCmd(cmd *cobra.Command, args []string) error {
closeID := args[0]
@@ -42,14 +38,27 @@ func runCloseCmd(cmd *cobra.Command, args []string) error {
return fmt.Errorf("issue with ID %s not found", closeID)
}
- // Ask for closing reason
- if err = huh.NewInput().Value(&issue.CloseReason).
- Title("Reason for closing the issue?").WithTheme(huh.ThemeBase()).Run(); err != nil {
+ closeReason := ""
+ if err = huh.NewSelect[string]().Value(&closeReason).
+ Title("Reason for closing the issue?").
+ Options(
+ huh.NewOption("Done", "Done"),
+ huh.NewOption("Duplicate issue", "Duplicate issue"),
+ huh.NewOption("Won't fix", "Won't fix"),
+ huh.NewOption("Obsolete", "Obsolete"),
+ huh.NewOption("Other", "Other"),
+ ).WithTheme(huh.ThemeBase()).Run(); err != nil {
return fmt.Errorf("error getting close reason: %w", err)
}
- // Close the issue.
- err = app.Issues.CloseIssue(cmd.Context(), closeID, issue.CloseReason, "", "")
+ if closeReason == "Other" {
+ if err = huh.NewInput().Value(&closeReason).
+ Title("Enter closing reason:").WithTheme(huh.ThemeBase()).Run(); err != nil {
+ return fmt.Errorf("error getting close reason: %w", err)
+ }
+ }
+
+ err = app.Issues.CloseIssue(cmd.Context(), closeID, closeReason, "", "")
if err != nil {
return fmt.Errorf("error closing issue: %w", err)
}
diff --git a/pkg/web/components/close_issue_form.templ b/pkg/web/components/close_issue_form.templ
new file mode 100644
index 0000000..41961df
--- /dev/null
+++ b/pkg/web/components/close_issue_form.templ
@@ -0,0 +1,33 @@
+package components
+
+import "github.com/LazyBachelor/LazyPM/pkg/web/components/base"
+
+type CloseIssueFormProps struct {
+ PostAction string
+}
+
+templ CloseIssueForm(props CloseIssueFormProps) {
+
+}
diff --git a/pkg/web/components/close_issue_form_templ.go b/pkg/web/components/close_issue_form_templ.go
new file mode 100644
index 0000000..e46ee65
--- /dev/null
+++ b/pkg/web/components/close_issue_form_templ.go
@@ -0,0 +1,79 @@
+// Code generated by templ - DO NOT EDIT.
+
+// templ: version: v0.3.977
+package components
+
+//lint:file-ignore SA4006 This context is only used if a nested component is present.
+
+import "github.com/a-h/templ"
+import templruntime "github.com/a-h/templ/runtime"
+
+import "github.com/LazyBachelor/LazyPM/pkg/web/components/base"
+
+type CloseIssueFormProps struct {
+ PostAction string
+}
+
+func CloseIssueForm(props CloseIssueFormProps) templ.Component {
+ return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
+ return templ_7745c5c3_CtxErr
+ }
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var1 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var1 == nil {
+ templ_7745c5c3_Var1 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+}
+
+var _ = templruntime.GeneratedTemplate
diff --git a/pkg/web/handler/issues.go b/pkg/web/handler/issues.go
index 76f1e94..3e81016 100644
--- a/pkg/web/handler/issues.go
+++ b/pkg/web/handler/issues.go
@@ -213,6 +213,41 @@ func DeleteIssue(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNoContent)
}
+func CloseIssue(w http.ResponseWriter, r *http.Request) {
+ if r.Method != http.MethodPost {
+ http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
+ return
+ }
+
+ issue := r.Context().Value(issueKey).(*models.Issue)
+ closeReason := r.FormValue("close_reason")
+
+ if closeReason == "" {
+ if HTMX(r).IsHxRequest() {
+ HTMX(r).WriteString("Closing reason is required
")
+ } else {
+ http.Error(w, "Closing reason is required", http.StatusBadRequest)
+ }
+ return
+ }
+
+ if err := App(r).Issues.CloseIssue(r.Context(), issue.ID, closeReason, "web", ""); err != nil {
+ if HTMX(r).IsHxRequest() {
+ HTMX(r).WriteString("Failed to close issue: " + err.Error() + "
")
+ } else {
+ http.Error(w, "Failed to close issue: "+err.Error(), http.StatusInternalServerError)
+ }
+ return
+ }
+
+ if HTMX(r).IsHxRequest() {
+ w.Header().Set("HX-Refresh", "true")
+ return
+ }
+
+ w.WriteHeader(http.StatusNoContent)
+}
+
func (f *IssueForm) toIssue() *models.Issue {
return &models.Issue{
Title: f.Title,
diff --git a/pkg/web/handler/modals.go b/pkg/web/handler/modals.go
index c4e7fc3..b3fd04b 100644
--- a/pkg/web/handler/modals.go
+++ b/pkg/web/handler/modals.go
@@ -72,3 +72,29 @@ func AssigneeFormModal(w http.ResponseWriter, r *http.Request) {
})
modal.Render(r.Context(), w)
}
+
+func CloseIssueFormModal(w http.ResponseWriter, r *http.Request) {
+ issue := r.Context().Value(issueKey).(*models.Issue)
+
+ if issue == nil {
+ http.Error(w, "Issue not found in context", http.StatusInternalServerError)
+ return
+ }
+
+ if issue.Status == models.StatusClosed {
+ http.Error(w, "Issue is already closed", http.StatusBadRequest)
+ return
+ }
+
+ modalContent := components.CloseIssueForm(components.CloseIssueFormProps{
+ PostAction: "/issues/" + issue.ID + "/close",
+ })
+
+ modal := components.Modal(components.ModalProps{
+ ID: "close-issue-modal",
+ Title: "Close Issue",
+ Content: modalContent,
+ Open: true,
+ })
+ modal.Render(r.Context(), w)
+}
diff --git a/pkg/web/routes/dashboard.templ b/pkg/web/routes/dashboard.templ
index 7dbf3c7..db4205e 100644
--- a/pkg/web/routes/dashboard.templ
+++ b/pkg/web/routes/dashboard.templ
@@ -55,6 +55,17 @@ templ DashboardContent(props DashboardProps) {
>
Edit
+ if props.SelectedIssue.Status != "closed" {
+
+ Close issue
+
+ }
Edit Edit ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- var templ_7745c5c3_Var5 string
- templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs("/issues/" + props.SelectedIssue.ID)
- if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/routes/dashboard.templ`, Line: 61, Col: 52}
+ if props.SelectedIssue.Status != "closed" {
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "Close issue ")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
- if templ_7745c5c3_Err != nil {
- return templ_7745c5c3_Err
- }
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "\" hx-confirm=\"Are you sure you want to delete this issue? This action cannot be undone.\" hx-target=\"body\">Delete issue Details ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "\" hx-confirm=\"Are you sure you want to delete this issue? This action cannot be undone.\" hx-target=\"body\">Delete issue
Details ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -164,12 +187,12 @@ func DashboardContent(props DashboardProps) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -193,12 +216,12 @@ func DashboardIssueList(issues []*models.Issue, selectedID string) templ.Compone
}()
}
ctx = templ.InitializeContext(ctx)
- templ_7745c5c3_Var7 := templ.GetChildren(ctx)
- if templ_7745c5c3_Var7 == nil {
- templ_7745c5c3_Var7 = templ.NopComponent
+ templ_7745c5c3_Var8 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var8 == nil {
+ templ_7745c5c3_Var8 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -218,7 +241,7 @@ func DashboardIssueList(issues []*models.Issue, selectedID string) templ.Compone
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, " ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -242,13 +265,13 @@ func DashboardIssueRows(issues []*models.Issue, selectedID string) templ.Compone
}()
}
ctx = templ.InitializeContext(ctx)
- templ_7745c5c3_Var8 := templ.GetChildren(ctx)
- if templ_7745c5c3_Var8 == nil {
- templ_7745c5c3_Var8 = templ.NopComponent
+ templ_7745c5c3_Var9 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var9 == nil {
+ templ_7745c5c3_Var9 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
if len(issues) == 0 {
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "No issues ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "No issues ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -258,64 +281,64 @@ func DashboardIssueRows(issues []*models.Issue, selectedID string) templ.Compone
if selectedID == issue.ID {
active = "bg-primary/10"
}
- var templ_7745c5c3_Var9 = []any{"hover cursor-pointer min-h-full w-full", active}
- templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var9...)
+ var templ_7745c5c3_Var10 = []any{"hover cursor-pointer min-h-full w-full", active}
+ templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var10...)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "\" hx-target=\"main\" hx-vals=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var12 string
- templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(issue.ID)
+ templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(`{"selected-issue": "` + issue.ID + `"}`)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/routes/dashboard.templ`, Line: 120, Col: 43}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/routes/dashboard.templ`, Line: 128, Col: 53}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, " ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "\" hx-swap=\"innerHTML\"> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var13 string
- templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(issue.Title)
+ templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(issue.ID)
if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/routes/dashboard.templ`, Line: 121, Col: 46}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/routes/dashboard.templ`, Line: 131, Col: 43}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, " ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, " ")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var14 string
+ templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(issue.Title)
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/routes/dashboard.templ`, Line: 132, Col: 46}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -323,7 +346,7 @@ func DashboardIssueRows(issues []*models.Issue, selectedID string) templ.Compone
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, " ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -331,7 +354,7 @@ func DashboardIssueRows(issues []*models.Issue, selectedID string) templ.Compone
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, " ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, " ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -339,7 +362,7 @@ func DashboardIssueRows(issues []*models.Issue, selectedID string) templ.Compone
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, " ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
diff --git a/pkg/web/routes/issue_detail.templ b/pkg/web/routes/issue_detail.templ
index 676ed3b..1ba3128 100644
--- a/pkg/web/routes/issue_detail.templ
+++ b/pkg/web/routes/issue_detail.templ
@@ -32,6 +32,17 @@ templ IssueDetailContent(props IssueDetailProps) {
>
Edit
+ if props.Issue.Status != "closed" {
+
+ Close issue
+
+ }
Edit Edit ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- var templ_7745c5c3_Var3 string
- templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs("/issues/" + props.Issue.ID)
- if templ_7745c5c3_Err != nil {
- return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/routes/issue_detail.templ`, Line: 38, Col: 43}
+ if props.Issue.Status != "closed" {
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "Close issue ")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "Delete issue ")
+ var templ_7745c5c3_Var4 string
+ templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs("/issues/" + props.Issue.ID)
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/routes/issue_detail.templ`, Line: 49, Col: 43}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "\" hx-confirm=\"Are you sure you want to delete this issue? This action cannot be undone.\" hx-target=\"body\">Delete issue
")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -81,7 +104,7 @@ func IssueDetailContent(props IssueDetailProps) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "
")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -92,7 +115,7 @@ func IssueDetailContent(props IssueDetailProps) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@@ -116,12 +139,12 @@ func IssueDetail(props IssueDetailProps) templ.Component {
}()
}
ctx = templ.InitializeContext(ctx)
- templ_7745c5c3_Var4 := templ.GetChildren(ctx)
- if templ_7745c5c3_Var4 == nil {
- templ_7745c5c3_Var4 = templ.NopComponent
+ templ_7745c5c3_Var5 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var5 == nil {
+ templ_7745c5c3_Var5 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
- templ_7745c5c3_Var5 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_Var6 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
@@ -139,7 +162,7 @@ func IssueDetail(props IssueDetailProps) templ.Component {
}
return nil
})
- templ_7745c5c3_Err = BaseLayout().Render(templ.WithChildren(ctx, templ_7745c5c3_Var5), templ_7745c5c3_Buffer)
+ templ_7745c5c3_Err = BaseLayout().Render(templ.WithChildren(ctx, templ_7745c5c3_Var6), templ_7745c5c3_Buffer)
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
diff --git a/pkg/web/server/routes.go b/pkg/web/server/routes.go
index 236d917..c1bf850 100644
--- a/pkg/web/server/routes.go
+++ b/pkg/web/server/routes.go
@@ -47,6 +47,8 @@ func (s *Server) RegisterRoutes(assets embed.FS) http.Handler {
r.Get("/", handler.GetIssue)
r.Patch("/", handler.UpdateIssue)
r.Get("/edit", handler.EditIssueFormModal)
+ r.Get("/close", handler.CloseIssueFormModal)
+ r.Post("/close", handler.CloseIssue)
r.Get("/assignee", handler.AssigneeFormModal)
r.Patch("/assignee", handler.UpdateAssignee)
r.Delete("/", handler.DeleteIssue)