Merge remote-tracking branch 'origin/main' into LPM-115
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/LazyBachelor/LazyPM/internal/models"
|
||||
@@ -20,19 +22,72 @@ func DashboardHandler(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// Check if board view is requested
|
||||
isBoardView := r.URL.Query().Get("board") == "true"
|
||||
sort.Slice(issues, func(i, j int) bool {
|
||||
return issues[i].Priority > issues[j].Priority
|
||||
})
|
||||
|
||||
isBoardView := r.URL.Query().Get("board") == "true" || strings.HasPrefix(r.URL.Path, "/board/")
|
||||
|
||||
if isBoardView {
|
||||
ctx := r.Context()
|
||||
|
||||
backlogIssues, err := app.Issues.GetIssuesNotInAnySprint(ctx)
|
||||
if err != nil {
|
||||
backlogIssues = []*models.Issue{}
|
||||
}
|
||||
|
||||
sort.Slice(backlogIssues, func(i, j int) bool {
|
||||
return backlogIssues[i].Priority > backlogIssues[j].Priority
|
||||
})
|
||||
|
||||
sprints, err := app.Issues.GetSprints(ctx)
|
||||
if err != nil {
|
||||
sprints = []int{}
|
||||
}
|
||||
|
||||
currentSprint := 0
|
||||
sprintParam := r.URL.Query().Get("sprint")
|
||||
if sprintParam != "" {
|
||||
fmt.Sscanf(sprintParam, "%d", ¤tSprint)
|
||||
}
|
||||
|
||||
if currentSprint == 0 && len(sprints) > 0 {
|
||||
currentSprint = sprints[0]
|
||||
}
|
||||
|
||||
var sprintIssues []*models.Issue
|
||||
if currentSprint > 0 {
|
||||
sprintIssues, err = app.Issues.GetIssuesBySprint(ctx, currentSprint)
|
||||
if err != nil {
|
||||
sprintIssues = []*models.Issue{}
|
||||
}
|
||||
}
|
||||
|
||||
sort.Slice(sprintIssues, func(i, j int) bool {
|
||||
return sprintIssues[i].Priority > sprintIssues[j].Priority
|
||||
})
|
||||
|
||||
boardProps := routes.BoardViewProps{
|
||||
BaseURL: "/?board=true",
|
||||
QueryParam: query,
|
||||
Issues: issues,
|
||||
EmptyText: "No issues found",
|
||||
BaseURL: "/?board=true",
|
||||
QueryParam: query,
|
||||
Issues: issues,
|
||||
BacklogIssues: backlogIssues,
|
||||
SprintIssues: sprintIssues,
|
||||
CurrentSprint: currentSprint,
|
||||
Sprints: sprints,
|
||||
EmptyText: "No issues found",
|
||||
}
|
||||
|
||||
if hx.IsHxRequest() {
|
||||
routes.BoardViewContent(boardProps).Render(r.Context(), w)
|
||||
if r.URL.Query().Get("board") != "true" {
|
||||
w.Header().Set("HX-Push-Url", fmt.Sprintf("/?board=true&sprint=%d", currentSprint))
|
||||
}
|
||||
|
||||
if strings.HasPrefix(r.URL.Path, "/board/") {
|
||||
routes.BoardColumns(boardProps).Render(r.Context(), w)
|
||||
} else {
|
||||
routes.BoardViewContent(boardProps).Render(r.Context(), w)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -40,7 +95,6 @@ func DashboardHandler(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// Regular dashboard view
|
||||
var selectedIssue *models.Issue
|
||||
if len(issues) > 0 {
|
||||
selectedIssue = issues[0]
|
||||
@@ -56,6 +110,13 @@ func DashboardHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
if selectedIssue != nil {
|
||||
comments, err := app.Issues.GetIssueComments(r.Context(), selectedIssue.ID)
|
||||
if err == nil && comments != nil {
|
||||
selectedIssue.Comments = comments
|
||||
}
|
||||
}
|
||||
|
||||
isPartial := r.URL.Query().Get("partial") == "true"
|
||||
if hx.IsHxRequest() && isPartial {
|
||||
routes.DashboardIssueList(issues, selectedIssueID).Render(r.Context(), w)
|
||||
@@ -77,3 +138,18 @@ func DashboardHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
routes.Dashboard(props).Render(r.Context(), w)
|
||||
}
|
||||
|
||||
func CreateSprintHandler(w http.ResponseWriter, r *http.Request) {
|
||||
app := App(r)
|
||||
ctx := r.Context()
|
||||
|
||||
sprintNum, err := app.Issues.AddSprint(ctx)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to create sprint", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
redirectURL := fmt.Sprintf("/?board=true&sprint=%d", sprintNum)
|
||||
w.Header().Set("HX-Redirect", redirectURL)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
@@ -2,8 +2,10 @@ package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"html"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/LazyBachelor/LazyPM/internal/models"
|
||||
@@ -18,7 +20,7 @@ const commentsKey = "comments"
|
||||
type IssueForm struct {
|
||||
Title string `form:"title" validate:"required,max=255"`
|
||||
Description string `form:"description" validate:"max=2000"`
|
||||
Status models.Status `form:"status" validate:"required,oneof=open in_progress blocked ready_to_sprint closed"`
|
||||
Status models.Status `form:"status" validate:"required,oneof=open in_progress blocked closed"`
|
||||
IssueType models.IssueType `form:"issue_type" validate:"required,oneof=task bug feature chore"`
|
||||
Priority int `form:"priority" validate:"gte=0,lte=4"`
|
||||
}
|
||||
@@ -26,10 +28,11 @@ type IssueForm struct {
|
||||
type UpdateIssueForm struct {
|
||||
Title *string `form:"title" validate:"omitempty,max=255"`
|
||||
Description *string `form:"description" validate:"omitempty,max=2000"`
|
||||
Status *models.Status `form:"status" validate:"omitempty,oneof=open in_progress blocked ready_to_sprint closed"`
|
||||
Status *models.Status `form:"status" validate:"omitempty,oneof=open in_progress blocked closed"`
|
||||
CloseReason *string `form:"close_reason" validate:"omitempty,max=2000"`
|
||||
IssueType *models.IssueType `form:"issue_type" validate:"omitempty,oneof=task bug feature chore"`
|
||||
Priority *int `form:"priority" validate:"omitempty,gte=0,lte=4"`
|
||||
Assignee *string `form:"assignee" validate:"omitempty,max=100"`
|
||||
}
|
||||
|
||||
func CreateIssue(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -63,7 +66,7 @@ func CreateIssue(w http.ResponseWriter, r *http.Request) {
|
||||
from := r.URL.Query().Get("from")
|
||||
referer := r.Header.Get("Referer")
|
||||
boardView := from == "board" || strings.Contains(referer, "board=true")
|
||||
|
||||
|
||||
if boardView {
|
||||
w.Header().Set("HX-Redirect", "/?board=true")
|
||||
} else {
|
||||
@@ -123,20 +126,8 @@ func GetIssue(w http.ResponseWriter, r *http.Request) {
|
||||
comments := r.Context().Value(commentsKey).([]*models.Comment)
|
||||
hx := HTMX(r)
|
||||
|
||||
from := r.URL.Query().Get("from")
|
||||
detailProps := routes.IssueDetailProps{
|
||||
Issue: issue,
|
||||
Comments: comments,
|
||||
From: from,
|
||||
}
|
||||
|
||||
if !hx.IsHxRequest() && strings.Contains(r.Header.Get("Accept"), "text/html") {
|
||||
routes.IssueDetail(detailProps).Render(r.Context(), w)
|
||||
return
|
||||
}
|
||||
|
||||
if hx.IsHxRequest() {
|
||||
routes.IssueDetailContent(detailProps).Render(r.Context(), w)
|
||||
if strings.Contains(r.Header.Get("Accept"), "text/html") && !hx.IsHxRequest() {
|
||||
routes.IssueDetailPage(issue, comments).Render(r.Context(), w)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -171,6 +162,24 @@ func UpdateIssue(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
ctx := r.Context()
|
||||
addToSprint := r.FormValue("add_to_sprint")
|
||||
removeFromSprint := r.FormValue("remove_from_sprint")
|
||||
|
||||
if addToSprint != "" {
|
||||
sprintNum := 0
|
||||
fmt.Sscanf(addToSprint, "%d", &sprintNum)
|
||||
if sprintNum > 0 {
|
||||
app.Issues.AddIssueToSprint(ctx, issue.ID, sprintNum)
|
||||
}
|
||||
} else if removeFromSprint != "" {
|
||||
sprintNum := 0
|
||||
fmt.Sscanf(removeFromSprint, "%d", &sprintNum)
|
||||
if sprintNum > 0 {
|
||||
app.Issues.RemoveIssueFromSprint(ctx, issue.ID, sprintNum)
|
||||
}
|
||||
}
|
||||
|
||||
issue, err = app.Issues.GetIssue(r.Context(), issue.ID)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to retrieve updated issue", http.StatusInternalServerError)
|
||||
@@ -178,13 +187,20 @@ func UpdateIssue(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
if hx.IsHxRequest() {
|
||||
// Check if 'from' parameter says board view
|
||||
from := r.URL.Query().Get("from")
|
||||
referer := r.Header.Get("Referer")
|
||||
boardView := from == "board" || strings.Contains(referer, "board=true")
|
||||
|
||||
|
||||
if boardView {
|
||||
w.Header().Set("HX-Redirect", "/?board=true")
|
||||
redirectURL := "/?board=true"
|
||||
if strings.Contains(referer, "sprint=") {
|
||||
if parsedURL, err := url.Parse(referer); err == nil {
|
||||
if sprintParam := parsedURL.Query().Get("sprint"); sprintParam != "" {
|
||||
redirectURL = redirectURL + "&sprint=" + sprintParam
|
||||
}
|
||||
}
|
||||
}
|
||||
w.Header().Set("HX-Redirect", redirectURL)
|
||||
} else {
|
||||
w.Header().Set("HX-Redirect", "/?selected-issue="+issue.ID)
|
||||
}
|
||||
@@ -197,7 +213,12 @@ func UpdateIssue(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
func UpdateAssignee(w http.ResponseWriter, r *http.Request) {
|
||||
issue := r.Context().Value(issueKey).(*models.Issue)
|
||||
assignee := r.FormValue("assignee")
|
||||
assignMe := r.FormValue("assign_me")
|
||||
|
||||
assignee := ""
|
||||
if assignMe != "" {
|
||||
assignee = "Me"
|
||||
}
|
||||
|
||||
if err := App(r).Issues.UpdateIssue(r.Context(), issue.ID, map[string]any{"assignee": assignee}, ""); err != nil {
|
||||
http.Error(w, "Failed to update assignee", http.StatusInternalServerError)
|
||||
@@ -214,7 +235,7 @@ func UpdateAssignee(w http.ResponseWriter, r *http.Request) {
|
||||
// Check if we're in board view
|
||||
referer := r.Header.Get("Referer")
|
||||
boardView := strings.Contains(referer, "board=true")
|
||||
|
||||
|
||||
if boardView {
|
||||
w.Header().Set("HX-Redirect", "/?board=true&selected-issue="+issue.ID)
|
||||
} else {
|
||||
@@ -387,7 +408,7 @@ func DeleteIssue(w http.ResponseWriter, r *http.Request) {
|
||||
from := r.URL.Query().Get("from")
|
||||
referer := r.Header.Get("Referer")
|
||||
boardView := from == "board" || strings.Contains(referer, "board=true")
|
||||
|
||||
|
||||
if boardView {
|
||||
w.Header().Set("HX-Redirect", "/?board=true")
|
||||
} else {
|
||||
@@ -472,5 +493,8 @@ func (f *UpdateIssueForm) toChanges() map[string]any {
|
||||
if f.Priority != nil {
|
||||
changes["priority"] = *f.Priority
|
||||
}
|
||||
if f.Assignee != nil {
|
||||
changes["assignee"] = *f.Assignee
|
||||
}
|
||||
return changes
|
||||
}
|
||||
|
||||
@@ -30,11 +30,15 @@ func HandleTaskStatus(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
hx := HTMX(r)
|
||||
if hx.IsHxRequest() {
|
||||
hx.WriteString(`
|
||||
if taskFeedback.Success {
|
||||
w.Header().Set("HX-Trigger", "task-status-success")
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
} else {
|
||||
hx.WriteString(`
|
||||
<div id="status" type="button" hx-get="/status" hx-target="#status" hx-swap="outerHTML">
|
||||
<button class="btn btn-error btn-sm" hx-get="/status/modal" hx-target="#modal-container" hx-swap="innerHTML">` + taskFeedback.Message + `</button>
|
||||
</div>`)
|
||||
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user