diff --git a/cmd/pm/tasks/backlogRefinement.go b/cmd/pm/tasks/backlogRefinement.go index be536b5..f1ed46b 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,65 @@ 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?"). + Key("how-many-duplicate-issues"). + 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", "how-many-duplicate-issues"} +} + 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 +128,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/go.mod b/go.mod index ff1e7b2..a9ecc2f 100644 --- a/go.mod +++ b/go.mod @@ -25,7 +25,7 @@ require ( // Web dependencies require ( github.com/NYTimes/gziphandler v1.1.1 - github.com/a-h/templ v0.3.977 + github.com/a-h/templ v0.3.1001 github.com/donseba/go-htmx v1.12.1 github.com/go-chi/chi/v5 v5.2.5 github.com/go-playground/form/v4 v4.3.0 @@ -69,7 +69,7 @@ require ( github.com/go-viper/mapstructure/v2 v2.5.0 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/golang/snappy v0.0.4 // indirect - github.com/haatos/goshipit v0.0.0-20260206030541-056850f43320 // indirect + github.com/haatos/goshipit v0.0.0-20260305043009-36e5c9a2e5c6 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/kevinburke/ssh_config v1.5.0 // indirect github.com/klauspost/compress v1.18.0 // indirect diff --git a/go.sum b/go.sum index 9e37a12..d159960 100644 --- a/go.sum +++ b/go.sum @@ -10,8 +10,8 @@ github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBi github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE= github.com/a-h/parse v0.0.0-20250122154542-74294addb73e h1:HjVbSQHy+dnlS6C3XajZ69NYAb5jbGNfHanvm1+iYlo= github.com/a-h/parse v0.0.0-20250122154542-74294addb73e/go.mod h1:3mnrkvGpurZ4ZrTDbYU84xhwXW2TjTKShSwjRi2ihfQ= -github.com/a-h/templ v0.3.977 h1:kiKAPXTZE2Iaf8JbtM21r54A8bCNsncrfnokZZSrSDg= -github.com/a-h/templ v0.3.977/go.mod h1:oCZcnKRf5jjsGpf2yELzQfodLphd2mwecwG4Crk5HBo= +github.com/a-h/templ v0.3.1001 h1:yHDTgexACdJttyiyamcTHXr2QkIeVF1MukLy44EAhMY= +github.com/a-h/templ v0.3.1001/go.mod h1:oCZcnKRf5jjsGpf2yELzQfodLphd2mwecwG4Crk5HBo= github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ= github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= @@ -128,8 +128,8 @@ github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/haatos/goshipit v0.0.0-20260206030541-056850f43320 h1:sb7SfxZfN+U9OHC61tcS98Ge0zY9uEkW5CP6KB4YVHg= -github.com/haatos/goshipit v0.0.0-20260206030541-056850f43320/go.mod h1:LFP8N8y5ORkifb+LZuOVNZYlJuV3WqdXCjxX5pGUaNI= +github.com/haatos/goshipit v0.0.0-20260305043009-36e5c9a2e5c6 h1:Yakj3J1ZxYCpFtBHUVkSBNA80LK/8bNrl/LA8fSvvFw= +github.com/haatos/goshipit v0.0.0-20260305043009-36e5c9a2e5c6/go.mod h1:2A31H3xgQHTgNp8OlX7aliXaY3QFwtI7XHuaP8G1ZSw= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/kevinburke/ssh_config v1.5.0 h1:3cPZmE54xb5j3G5xQCjSvokqNwU2uW+3ry1+PRLSPpA= diff --git a/internal/commands/issues/close.go b/internal/commands/issues/close.go index 3c89b10..ce4bfcb 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,30 @@ 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) + } + if closeReason == "" { + return fmt.Errorf("closing reason cannot be empty when selecting 'Other'") + } + } + + 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/assignee_form_templ.go b/pkg/web/components/assignee_form_templ.go index 06f3ea1..ae68a4f 100644 --- a/pkg/web/components/assignee_form_templ.go +++ b/pkg/web/components/assignee_form_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.977 +// templ: version: v0.3.1001 package components //lint:file-ignore SA4006 This context is only used if a nested component is present. diff --git a/pkg/web/components/base/input_templ.go b/pkg/web/components/base/input_templ.go index 9d787ce..1b68a0c 100644 --- a/pkg/web/components/base/input_templ.go +++ b/pkg/web/components/base/input_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.977 +// templ: version: v0.3.1001 package base //lint:file-ignore SA4006 This context is only used if a nested component is present. diff --git a/pkg/web/components/base/range_templ.go b/pkg/web/components/base/range_templ.go index 4ae6395..1a57ebe 100644 --- a/pkg/web/components/base/range_templ.go +++ b/pkg/web/components/base/range_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.977 +// templ: version: v0.3.1001 package base //lint:file-ignore SA4006 This context is only used if a nested component is present. diff --git a/pkg/web/components/base/select_templ.go b/pkg/web/components/base/select_templ.go index aab85ee..a7b5040 100644 --- a/pkg/web/components/base/select_templ.go +++ b/pkg/web/components/base/select_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.977 +// templ: version: v0.3.1001 package base //lint:file-ignore SA4006 This context is only used if a nested component is present. diff --git a/pkg/web/components/base/table_templ.go b/pkg/web/components/base/table_templ.go index b7544a0..7259528 100644 --- a/pkg/web/components/base/table_templ.go +++ b/pkg/web/components/base/table_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.977 +// templ: version: v0.3.1001 package base //lint:file-ignore SA4006 This context is only used if a nested component is present. diff --git a/pkg/web/components/base/textarea_templ.go b/pkg/web/components/base/textarea_templ.go index 32f9357..0d1bf07 100644 --- a/pkg/web/components/base/textarea_templ.go +++ b/pkg/web/components/base/textarea_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.977 +// templ: version: v0.3.1001 package base //lint:file-ignore SA4006 This context is only used if a nested component is present. diff --git a/pkg/web/components/close_issue_form.templ b/pkg/web/components/close_issue_form.templ new file mode 100644 index 0000000..7c6a1c2 --- /dev/null +++ b/pkg/web/components/close_issue_form.templ @@ -0,0 +1,34 @@ +package components + +import "github.com/LazyBachelor/LazyPM/pkg/web/components/base" + +type CloseIssueFormProps struct { + PostAction string +} + +templ CloseIssueForm(props CloseIssueFormProps) { +
+
+ @base.Select(base.SelectProps{ + Name: "close_reason", + Label: "Reason for closing the issue", + Required: true, + Options: []base.SelectOption{ + {Label: "Done", Value: "Done"}, + {Label: "Duplicate issue", Value: "Duplicate issue"}, + {Label: "Won't fix", Value: "Won't fix"}, + {Label: "Obsolete", Value: "Obsolete"}, + {Label: "Other", Value: "Other"}, + }, + Size: "md", + }) + +
+} 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..1e2b421 --- /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.1001 +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 + } + templ_7745c5c3_Err = base.Select(base.SelectProps{ + Name: "close_reason", + Label: "Reason for closing the issue", + Required: true, + Options: []base.SelectOption{ + {Label: "Done", Value: "Done"}, + {Label: "Duplicate issue", Value: "Duplicate issue"}, + {Label: "Won't fix", Value: "Won't fix"}, + {Label: "Obsolete", Value: "Obsolete"}, + {Label: "Other", Value: "Other"}, + }, + Size: "md", + }).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/pkg/web/components/comment_templ.go b/pkg/web/components/comment_templ.go index e97390e..2542d51 100644 --- a/pkg/web/components/comment_templ.go +++ b/pkg/web/components/comment_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.977 +// templ: version: v0.3.1001 package components //lint:file-ignore SA4006 This context is only used if a nested component is present. diff --git a/pkg/web/components/header_templ.go b/pkg/web/components/header_templ.go index 4a6a7fa..a4c3217 100644 --- a/pkg/web/components/header_templ.go +++ b/pkg/web/components/header_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.977 +// templ: version: v0.3.1001 package components //lint:file-ignore SA4006 This context is only used if a nested component is present. diff --git a/pkg/web/components/icons_templ.go b/pkg/web/components/icons_templ.go index c8c421c..0df923b 100644 --- a/pkg/web/components/icons_templ.go +++ b/pkg/web/components/icons_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.977 +// templ: version: v0.3.1001 package components //lint:file-ignore SA4006 This context is only used if a nested component is present. diff --git a/pkg/web/components/issue_detail_templ.go b/pkg/web/components/issue_detail_templ.go index b9443b2..c3d4351 100644 --- a/pkg/web/components/issue_detail_templ.go +++ b/pkg/web/components/issue_detail_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.977 +// templ: version: v0.3.1001 package components //lint:file-ignore SA4006 This context is only used if a nested component is present. diff --git a/pkg/web/components/issue_templ.go b/pkg/web/components/issue_templ.go index 922d698..d055a8e 100644 --- a/pkg/web/components/issue_templ.go +++ b/pkg/web/components/issue_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.977 +// templ: version: v0.3.1001 package components //lint:file-ignore SA4006 This context is only used if a nested component is present. @@ -21,7 +21,7 @@ type IssueFormProps struct { Title string Description string Status string - CloseReason string // Shown when status is "closed" + CloseReason string // Set when status is "closed" Priority int IssueType string Class string @@ -68,14 +68,14 @@ func IssueForm(props IssueFormProps) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "\" x-data=\"") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "\" x-data=\"{ status: $el.dataset.status }\" data-status=\"") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var4 string - templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(`{ status: '` + props.Status + `' }`) + templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(props.Status) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/issue.templ`, Line: 25, Col: 101} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/issue.templ`, Line: 25, Col: 122} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) if templ_7745c5c3_Err != nil { @@ -248,7 +248,7 @@ func IssueForm(props IssueFormProps) templ.Component { var templ_7745c5c3_Var8 string templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(props.DeleteAction) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/issue.templ`, Line: 106, Col: 31} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/issue.templ`, Line: 107, Col: 31} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) if templ_7745c5c3_Err != nil { @@ -348,7 +348,7 @@ func IssueRows(issues []*models.Issue) templ.Component { var templ_7745c5c3_Var11 string templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs("/issues/" + issue.ID) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/issue.templ`, Line: 146, Col: 35} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/issue.templ`, Line: 147, Col: 35} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11)) if templ_7745c5c3_Err != nil { @@ -361,7 +361,7 @@ func IssueRows(issues []*models.Issue) templ.Component { var templ_7745c5c3_Var12 string templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(issue.ID) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/issue.templ`, Line: 150, Col: 15} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/issue.templ`, Line: 151, Col: 15} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12)) if templ_7745c5c3_Err != nil { @@ -374,7 +374,7 @@ func IssueRows(issues []*models.Issue) templ.Component { var templ_7745c5c3_Var13 string templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs("/issues/" + issue.ID) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/issue.templ`, Line: 155, Col: 35} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/issue.templ`, Line: 156, Col: 35} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13)) if templ_7745c5c3_Err != nil { @@ -387,7 +387,7 @@ func IssueRows(issues []*models.Issue) templ.Component { 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/components/issue.templ`, Line: 159, Col: 18} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/issue.templ`, Line: 160, Col: 18} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14)) if templ_7745c5c3_Err != nil { @@ -400,7 +400,7 @@ func IssueRows(issues []*models.Issue) templ.Component { var templ_7745c5c3_Var15 string templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(string(issue.Status)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/issue.templ`, Line: 161, Col: 54} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/issue.templ`, Line: 162, Col: 54} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15)) if templ_7745c5c3_Err != nil { @@ -413,7 +413,7 @@ func IssueRows(issues []*models.Issue) templ.Component { var templ_7745c5c3_Var16 string templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(string(issue.IssueType)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/issue.templ`, Line: 162, Col: 57} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/issue.templ`, Line: 163, Col: 57} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16)) if templ_7745c5c3_Err != nil { @@ -426,7 +426,7 @@ func IssueRows(issues []*models.Issue) templ.Component { var templ_7745c5c3_Var17 string templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("P%d", issue.Priority)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/issue.templ`, Line: 163, Col: 68} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/issue.templ`, Line: 164, Col: 68} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17)) if templ_7745c5c3_Err != nil { @@ -439,7 +439,7 @@ func IssueRows(issues []*models.Issue) templ.Component { var templ_7745c5c3_Var18 string templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs("/issues/" + issue.ID) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/issue.templ`, Line: 168, Col: 38} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/components/issue.templ`, Line: 169, Col: 38} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18)) if templ_7745c5c3_Err != nil { diff --git a/pkg/web/components/layout_templ.go b/pkg/web/components/layout_templ.go index 28d4c95..a426fce 100644 --- a/pkg/web/components/layout_templ.go +++ b/pkg/web/components/layout_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.977 +// templ: version: v0.3.1001 package components //lint:file-ignore SA4006 This context is only used if a nested component is present. diff --git a/pkg/web/components/modal_templ.go b/pkg/web/components/modal_templ.go index f67c331..b6fbc34 100644 --- a/pkg/web/components/modal_templ.go +++ b/pkg/web/components/modal_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.977 +// templ: version: v0.3.1001 package components //lint:file-ignore SA4006 This context is only used if a nested component is present. diff --git a/pkg/web/components/search_templ.go b/pkg/web/components/search_templ.go index f27707f..6fc7f33 100644 --- a/pkg/web/components/search_templ.go +++ b/pkg/web/components/search_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.977 +// templ: version: v0.3.1001 package components //lint:file-ignore SA4006 This context is only used if a nested component is present. diff --git a/pkg/web/components/sidebar_templ.go b/pkg/web/components/sidebar_templ.go index a66fd90..f26a365 100644 --- a/pkg/web/components/sidebar_templ.go +++ b/pkg/web/components/sidebar_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.977 +// templ: version: v0.3.1001 package components //lint:file-ignore SA4006 This context is only used if a nested component is present. diff --git a/pkg/web/components/status_templ.go b/pkg/web/components/status_templ.go index 451a387..41694c7 100644 --- a/pkg/web/components/status_templ.go +++ b/pkg/web/components/status_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.977 +// templ: version: v0.3.1001 package components //lint:file-ignore SA4006 This context is only used if a nested component is present. diff --git a/pkg/web/handler/issues.go b/pkg/web/handler/issues.go index 95c91f1..3306c7a 100644 --- a/pkg/web/handler/issues.go +++ b/pkg/web/handler/issues.go @@ -2,6 +2,7 @@ package handler import ( "context" + "html" "net/http" "strings" @@ -250,6 +251,49 @@ 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 + } + + issueVal := r.Context().Value(issueKey) + issue, ok := issueVal.(*models.Issue) + if !ok || issue == nil { + http.Error(w, "Issue not found in context", http.StatusInternalServerError) + return + } + + closeReason := r.FormValue("close_reason") + + if closeReason == "" { + if HTMX(r).IsHxRequest() { + w.WriteHeader(http.StatusBadRequest) + 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() { + w.WriteHeader(http.StatusInternalServerError) + HTMX(r).WriteString("
Failed to close issue: " + html.EscapeString(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 6438ce2..b9bb2fa 100644 --- a/pkg/web/handler/modals.go +++ b/pkg/web/handler/modals.go @@ -36,9 +36,9 @@ func CreateIssueFormModal(w http.ResponseWriter, r *http.Request) { } func EditIssueFormModal(w http.ResponseWriter, r *http.Request) { - issue := r.Context().Value(issueKey).(*models.Issue) - - if issue == nil { + issueVal := r.Context().Value(issueKey) + issue, ok := issueVal.(*models.Issue) + if !ok || issue == nil { http.Error(w, "Issue not found in context", http.StatusInternalServerError) return } @@ -76,9 +76,9 @@ func EditIssueFormModal(w http.ResponseWriter, r *http.Request) { } func AssigneeFormModal(w http.ResponseWriter, r *http.Request) { - issue := r.Context().Value(issueKey).(*models.Issue) - - if issue == nil { + issueVal := r.Context().Value(issueKey) + issue, ok := issueVal.(*models.Issue) + if !ok || issue == nil { http.Error(w, "Issue not found in context", http.StatusInternalServerError) return } @@ -126,3 +126,29 @@ func DeleteIssueConfirmModal(w http.ResponseWriter, r *http.Request) { confirmModal.Render(r.Context(), w) } + +func CloseIssueFormModal(w http.ResponseWriter, r *http.Request) { + issueVal := r.Context().Value(issueKey) + issue, ok := issueVal.(*models.Issue) + if !ok || 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/boardview_templ.go b/pkg/web/routes/boardview_templ.go index 4d224a2..406eedf 100644 --- a/pkg/web/routes/boardview_templ.go +++ b/pkg/web/routes/boardview_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.977 +// templ: version: v0.3.1001 package routes //lint:file-ignore SA4006 This context is only used if a nested component is present. diff --git a/pkg/web/routes/dashboard.templ b/pkg/web/routes/dashboard.templ index 83a5d13..fc09df2 100644 --- a/pkg/web/routes/dashboard.templ +++ b/pkg/web/routes/dashboard.templ @@ -63,7 +63,18 @@ templ DashboardContent(props DashboardProps) { > 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: 68, Col: 51} + if props.SelectedIssue.Status != "closed" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, " ") + var templ_7745c5c3_Var6 string + templ_7745c5c3_Var6, 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: 79, Col: 51} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "\" hx-target=\"main\" hx-swap=\"innerHTML\" hx-push-url=\"true\">Details") 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 } @@ -146,12 +169,12 @@ func DashboardContent(props DashboardProps) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if props.SelectedIssue != nil { - 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 } @@ -161,12 +184,12 @@ func DashboardContent(props DashboardProps) templ.Component { 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 } } - 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 } @@ -190,12 +213,12 @@ func DashboardIssueList(issues []*models.Issue, selectedID string) templ.Compone }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var6 := templ.GetChildren(ctx) - if templ_7745c5c3_Var6 == nil { - templ_7745c5c3_Var6 = templ.NopComponent + templ_7745c5c3_Var7 := templ.GetChildren(ctx) + if templ_7745c5c3_Var7 == nil { + templ_7745c5c3_Var7 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - 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 } @@ -215,7 +238,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, 12, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -239,13 +262,13 @@ func DashboardIssueRows(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) if len(issues) == 0 { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "No issues") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "No issues") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -255,77 +278,90 @@ func DashboardIssueRows(issues []*models.Issue, selectedID string) templ.Compone if selectedID == issue.ID { active = "bg-primary/10" } - var templ_7745c5c3_Var8 = []any{"hover cursor-pointer min-h-full w-full", active} - templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var8...) + 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...) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "\" hx-swap=\"innerHTML\" data-issue-id=\"") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var12 string templ_7745c5c3_Var12, 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: 134, Col: 43} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `pkg/web/routes/dashboard.templ`, Line: 142, Col: 27} } _, 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, 18, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "\">") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var14 string + templ_7745c5c3_Var14, 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: 152, Col: 43} + } + _, 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, 22, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var15 string + templ_7745c5c3_Var15, 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: 153, Col: 46} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -333,7 +369,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, 24, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -341,7 +377,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, 21, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -349,7 +385,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, 22, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "") 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 5706792..d483a83 100644 --- a/pkg/web/routes/issue_detail.templ +++ b/pkg/web/routes/issue_detail.templ @@ -47,6 +47,17 @@ templ IssueDetailContent(props IssueDetailProps) { > Edit + if props.Issue.Status != "closed" { + + }
@components.IssueDetail(components.IssueDetailProps{Issue: props.Issue}) diff --git a/pkg/web/routes/issue_detail_templ.go b/pkg/web/routes/issue_detail_templ.go index 1086c17..6139e82 100644 --- a/pkg/web/routes/issue_detail_templ.go +++ b/pkg/web/routes/issue_detail_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.977 +// templ: version: v0.3.1001 package routes //lint:file-ignore SA4006 This context is only used if a nested component is present. @@ -48,11 +48,9 @@ func IssueDetailContent(props IssueDetailProps) templ.Component { ctx = templ.ClearChildren(ctx) backURL := "/" editURL := "/issues/" + props.Issue.ID + "/edit" - deleteURL := "/issues/" + props.Issue.ID if props.From == "board" { backURL = "/?board=true" editURL += "?from=board" - deleteURL += "?from=board" } templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = components.CommentSection(components.CommentSectionProps{ - IssueID: props.Issue.ID, - Comments: props.Comments, - }).Render(ctx, templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "
") - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err - } - return nil - }) -} - -func IssueDetail(props IssueDetailProps) 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_Var4 := templ.GetChildren(ctx) - if templ_7745c5c3_Var4 == nil { - templ_7745c5c3_Var4 = templ.NopComponent - } - ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Var5 := 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 { - 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_Err = IssueDetailContent(props).Render(ctx, templ_7745c5c3_Buffer) + if props.Issue.Status != "closed" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - return nil - }) - templ_7745c5c3_Err = BaseLayout().Render(templ.WithChildren(ctx, templ_7745c5c3_Var5), templ_7745c5c3_Buffer) - if templ_7745c5c3_Err != nil { - return templ_7745c5c3_Err } - return nil - }) -} - -func IssueDetailModalContent(props IssueDetailModalProps) 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_Var6 := templ.GetChildren(ctx) - if templ_7745c5c3_Var6 == nil { - templ_7745c5c3_Var6 = templ.NopComponent - } - ctx = templ.ClearChildren(ctx) - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -210,4 +136,99 @@ func IssueDetailModalContent(props IssueDetailModalProps) templ.Component { }) } +func IssueDetail(props IssueDetailProps) 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_Var5 := templ.GetChildren(ctx) + if templ_7745c5c3_Var5 == nil { + templ_7745c5c3_Var5 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + 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 { + 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_Err = IssueDetailContent(props).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) + templ_7745c5c3_Err = BaseLayout().Render(templ.WithChildren(ctx, templ_7745c5c3_Var6), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func IssueDetailModalContent(props IssueDetailModalProps) 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_Var7 := templ.GetChildren(ctx) + if templ_7745c5c3_Var7 == nil { + templ_7745c5c3_Var7 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = components.IssueDetail(components.IssueDetailProps{Issue: props.Issue}).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = components.CommentSection(components.CommentSectionProps{ + IssueID: props.Issue.ID, + Comments: props.Comments, + }).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + var _ = templruntime.GeneratedTemplate diff --git a/pkg/web/routes/layout_templ.go b/pkg/web/routes/layout_templ.go index a43d6b1..88f4554 100644 --- a/pkg/web/routes/layout_templ.go +++ b/pkg/web/routes/layout_templ.go @@ -1,6 +1,6 @@ // Code generated by templ - DO NOT EDIT. -// templ: version: v0.3.977 +// templ: version: v0.3.1001 package routes //lint:file-ignore SA4006 This context is only used if a nested component is present. diff --git a/pkg/web/server/routes.go b/pkg/web/server/routes.go index 9004329..2c4b916 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("/delete", handler.DeleteIssueConfirmModal) r.Get("/assignee", handler.AssigneeFormModal) r.Patch("/assignee", handler.UpdateAssignee)