adding functionality for editing priority; adding priority to the list of displayed issues; sorting the list of issues by their priority

This commit is contained in:
viljarb
2026-02-23 15:56:16 +01:00
parent 7674ae3690
commit 970c735fa4
7 changed files with 103 additions and 12 deletions

View File

@@ -36,7 +36,7 @@ func (h HelpBar) shortHelp() string {
styles.HighlightKey("↓/j") + " down", styles.HighlightKey("↓/j") + " down",
styles.HighlightKey("pgup/pgdn") + " page", styles.HighlightKey("pgup/pgdn") + " page",
styles.HighlightKey("a") + " add", styles.HighlightKey("a") + " add",
styles.HighlightKey("e/d/s") + " edit", styles.HighlightKey("e/d/s/p") + " edit",
styles.HighlightKey("x") + " delete", styles.HighlightKey("x") + " delete",
styles.HighlightKey("q") + " quit", styles.HighlightKey("q") + " quit",
styles.HighlightKey("?") + " help", styles.HighlightKey("?") + " help",
@@ -75,8 +75,9 @@ func (h HelpBar) fullHelp() string {
renderRow("pgup", "page up", "pgdn", "page down"), renderRow("pgup", "page up", "pgdn", "page down"),
renderRow("b", "back to list", "a", "add issue"), renderRow("b", "back to list", "a", "add issue"),
renderRow("e", "edit title", "d", "edit desc"), renderRow("e", "edit title", "d", "edit desc"),
renderRow("s", "change status", "x", "delete issue"), renderRow("s", "change status", "p", "change priority"),
renderRow("?", "help", "q", "quit"), renderRow("x", "delete issue", "?", "help"),
renderRow("q", "quit", "", ""),
} }
content := lipgloss.JoinVertical(lipgloss.Left, rows...) content := lipgloss.JoinVertical(lipgloss.Left, rows...)
return lipgloss.NewStyle(). return lipgloss.NewStyle().

View File

@@ -1,6 +1,8 @@
package dashboard package dashboard
import ( import (
"fmt"
"github.com/LazyBachelor/LazyPM/internal/models" "github.com/LazyBachelor/LazyPM/internal/models"
"github.com/LazyBachelor/LazyPM/pkg/tui/styles" "github.com/LazyBachelor/LazyPM/pkg/tui/styles"
"github.com/charmbracelet/bubbles/viewport" "github.com/charmbracelet/bubbles/viewport"
@@ -53,6 +55,10 @@ func (i *IssueDetail) refreshContent() {
styles.LabelStyle.Render("Status:") + styles.StatusStyle(string(i.issue.Status)).Render(string(i.issue.Status)), styles.LabelStyle.Render("Status:") + styles.StatusStyle(string(i.issue.Status)).Render(string(i.issue.Status)),
) )
priorityRow := styles.RowStyle.Render(
styles.LabelStyle.Render("Priority:") + styles.ValueStyle.Render(fmt.Sprintf("%d", i.issue.Priority)),
)
descLabel := styles.LabelStyle.Render("Description:") descLabel := styles.LabelStyle.Render("Description:")
descContent := styles.ValueStyle.Render(i.issue.Description) descContent := styles.ValueStyle.Render(i.issue.Description)
@@ -61,6 +67,7 @@ func (i *IssueDetail) refreshContent() {
idRow, idRow,
typeRow, typeRow,
statusRow, statusRow,
priorityRow,
descLabel, descLabel,
descContent, descContent,
) )

View File

@@ -2,7 +2,9 @@ package dashboard
import ( import (
"context" "context"
"fmt"
"io" "io"
"sort"
"github.com/LazyBachelor/LazyPM/internal/models" "github.com/LazyBachelor/LazyPM/internal/models"
"github.com/LazyBachelor/LazyPM/internal/service" "github.com/LazyBachelor/LazyPM/internal/service"
@@ -53,6 +55,7 @@ func getTableColumns(width int) []TableColumn {
{width: 20, label: "TITLE", key: "title"}, {width: 20, label: "TITLE", key: "title"},
{width: 15, label: "STATUS", key: "status"}, {width: 15, label: "STATUS", key: "status"},
{width: 10, label: "TYPE", key: "type"}, {width: 10, label: "TYPE", key: "type"},
{width: 15, label: "PRIORITY", key: "priority"},
} }
} }
} }
@@ -139,6 +142,7 @@ func OpenAndInProgressOnly(issues []models.Issue) []models.Issue {
out = append(out, issue) out = append(out, issue)
} }
} }
sortByPriorityDesc(out)
return out return out
} }
@@ -150,9 +154,17 @@ func ClosedOnly(issues []models.Issue) []models.Issue {
out = append(out, issue) out = append(out, issue)
} }
} }
sortByPriorityDesc(out)
return out return out
} }
// sortByPriorityDesc sorts issues by priority, highest first.
func sortByPriorityDesc(issues []models.Issue) {
sort.Slice(issues, func(i, j int) bool {
return issues[i].Priority > issues[j].Priority
})
}
func (l *IssueList) Update(msg tea.Msg) (tea.Cmd, bool) { func (l *IssueList) Update(msg tea.Msg) (tea.Cmd, bool) {
var cmd tea.Cmd var cmd tea.Cmd
oldIndex := l.list.Index() oldIndex := l.list.Index()
@@ -311,6 +323,8 @@ func getColumnValue(col TableColumn, issue ListIssue) string {
return string(issue.Issue.Status) return string(issue.Issue.Status)
case "type": case "type":
return string(issue.Issue.IssueType) return string(issue.Issue.IssueType)
case "priority":
return fmt.Sprintf("%d", issue.Issue.Priority)
default: default:
return "" return ""
} }

View File

@@ -16,6 +16,7 @@ type DashboardKeyMap struct {
EditTitle key.Binding EditTitle key.Binding
EditDescription key.Binding EditDescription key.Binding
ChangeStatus key.Binding ChangeStatus key.Binding
ChangePriority key.Binding
AddIssue key.Binding AddIssue key.Binding
DeleteIssue key.Binding DeleteIssue key.Binding
} }
@@ -61,6 +62,10 @@ var defaultDashboardKeyMap = DashboardKeyMap{
key.WithKeys("s"), key.WithKeys("s"),
key.WithHelp("s", "change status"), key.WithHelp("s", "change status"),
), ),
ChangePriority: key.NewBinding(
key.WithKeys("p"),
key.WithHelp("p", "change priority"),
),
AddIssue: key.NewBinding( AddIssue: key.NewBinding(
key.WithKeys("a"), key.WithKeys("a"),
key.WithHelp("a", "add issue"), key.WithHelp("a", "add issue"),
@@ -89,24 +94,28 @@ func (d *Model) handleKeyMsg(msg tea.KeyMsg) tea.Cmd {
d.issueDetail.ScrollUp(1) d.issueDetail.ScrollUp(1)
case d.IsFocusedOnDetail() && key.Matches(msg, d.keyMap.ScrollDown): case d.IsFocusedOnDetail() && key.Matches(msg, d.keyMap.ScrollDown):
d.issueDetail.ScrollDown(1) d.issueDetail.ScrollDown(1)
case !d.editingTitle && !d.creatingIssue && !d.editingDescription && !d.choosingStatus && !d.confirmingDelete && key.Matches(msg, d.keyMap.EditTitle): case !d.editingTitle && !d.creatingIssue && !d.editingDescription && !d.choosingStatus && !d.choosingPriority && !d.confirmingDelete && key.Matches(msg, d.keyMap.EditTitle):
if selected := d.FocusedIssueList().SelectedItem(); selected.ID != "" { if selected := d.FocusedIssueList().SelectedItem(); selected.ID != "" {
d.startEditTitle(selected) d.startEditTitle(selected)
cmd = d.titleInput.Focus() cmd = d.titleInput.Focus()
} }
case !d.editingTitle && !d.creatingIssue && !d.editingDescription && !d.choosingStatus && !d.confirmingDelete && key.Matches(msg, d.keyMap.EditDescription): case !d.editingTitle && !d.creatingIssue && !d.editingDescription && !d.choosingStatus && !d.choosingPriority && !d.confirmingDelete && key.Matches(msg, d.keyMap.EditDescription):
if selected := d.FocusedIssueList().SelectedItem(); selected.ID != "" { if selected := d.FocusedIssueList().SelectedItem(); selected.ID != "" {
d.startEditDescription(selected) d.startEditDescription(selected)
cmd = d.descriptionInput.Focus() cmd = d.descriptionInput.Focus()
} }
case !d.editingTitle && !d.creatingIssue && !d.editingDescription && !d.choosingStatus && !d.confirmingDelete && key.Matches(msg, d.keyMap.ChangeStatus): case !d.editingTitle && !d.creatingIssue && !d.editingDescription && !d.choosingStatus && !d.choosingPriority && !d.confirmingDelete && key.Matches(msg, d.keyMap.ChangeStatus):
if selected := d.FocusedIssueList().SelectedItem(); selected.ID != "" { if selected := d.FocusedIssueList().SelectedItem(); selected.ID != "" {
d.startChooseStatus(selected) d.startChooseStatus(selected)
} }
case !d.editingTitle && !d.creatingIssue && !d.editingDescription && !d.choosingStatus && !d.confirmingDelete && key.Matches(msg, d.keyMap.AddIssue): case !d.editingTitle && !d.creatingIssue && !d.editingDescription && !d.choosingStatus && !d.choosingPriority && !d.confirmingDelete && key.Matches(msg, d.keyMap.ChangePriority):
if selected := d.FocusedIssueList().SelectedItem(); selected.ID != "" {
d.startChoosePriority(selected)
}
case !d.editingTitle && !d.creatingIssue && !d.editingDescription && !d.choosingStatus && !d.choosingPriority && !d.confirmingDelete && key.Matches(msg, d.keyMap.AddIssue):
d.startCreateIssue() d.startCreateIssue()
cmd = d.createTitleInput.Focus() cmd = d.createTitleInput.Focus()
case !d.editingTitle && !d.creatingIssue && !d.editingDescription && !d.choosingStatus && !d.confirmingDelete && key.Matches(msg, d.keyMap.DeleteIssue): case !d.editingTitle && !d.creatingIssue && !d.editingDescription && !d.choosingStatus && !d.choosingPriority && !d.confirmingDelete && key.Matches(msg, d.keyMap.DeleteIssue):
fl := d.FocusedIssueList() fl := d.FocusedIssueList()
if selected := fl.SelectedItem(); selected.ID != "" { if selected := fl.SelectedItem(); selected.ID != "" {
d.startConfirmDelete(selected.ID, fl.Index()) d.startConfirmDelete(selected.ID, fl.Index())

View File

@@ -24,9 +24,9 @@ type Model struct {
svc *service.Services svc *service.Services
width int width int
height int height int
focusedWindow int // 0 = main (display issues), 1 = closed issues focusedWindow int // 0 = main (display issues), 1 = closed issues
focusedPaneMain int // 0 = list, 1 = detail focusedPaneMain int // 0 = list, 1 = detail
focusedPaneClosed int focusedPaneClosed int
editingTitle bool // true while we are editing a title editingTitle bool // true while we are editing a title
titleInput textinput.Model titleInput textinput.Model
editingIssueID string editingIssueID string
@@ -41,8 +41,10 @@ type Model struct {
deleteConfirmID string deleteConfirmID string
deleteConfirmIndex int deleteConfirmIndex int
choosingStatus bool choosingStatus bool
statusIssueID string statusIssueID string
choosingPriority bool
priorityIssueID string
feedbackChan chan task.ValidationFeedback feedbackChan chan task.ValidationFeedback
quitChan chan bool quitChan chan bool
currentFeedback task.ValidationFeedback currentFeedback task.ValidationFeedback
@@ -125,6 +127,11 @@ func (m *Model) startChooseStatus(selected ListIssue) {
m.statusIssueID = selected.ID m.statusIssueID = selected.ID
} }
func (m *Model) startChoosePriority(selected ListIssue) {
m.choosingPriority = true
m.priorityIssueID = selected.ID
}
func (m *Model) Init() tea.Cmd { func (m *Model) Init() tea.Cmd {
return m.listenForValidation() return m.listenForValidation()
} }

View File

@@ -24,6 +24,11 @@ type issueStatusUpdatedMsg struct {
Err error Err error
} }
type issuePriorityUpdatedMsg struct {
IssueID string
Err error
}
type selectIssueMsg struct { type selectIssueMsg struct {
IssueID string IssueID string
} }
@@ -63,6 +68,14 @@ func updateIssueStatusCmd(svc *service.Services, issueID, status string) tea.Cmd
} }
} }
func updateIssuePriorityCmd(svc *service.Services, issueID string, priority int) tea.Cmd {
return func() tea.Msg {
updates := map[string]interface{}{"priority": priority}
err := svc.Beads.UpdateIssue(context.Background(), issueID, updates, "tui")
return issuePriorityUpdatedMsg{IssueID: issueID, Err: err}
}
}
func createIssueCmd(svc *service.Services, title string) tea.Cmd { func createIssueCmd(svc *service.Services, title string) tea.Cmd {
return func() tea.Msg { return func() tea.Msg {
issue := &models.Issue{ issue := &models.Issue{
@@ -129,6 +142,14 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
} }
return m, m.refreshIssueListsAndSelectIssue(msg.IssueID) return m, m.refreshIssueListsAndSelectIssue(msg.IssueID)
case issuePriorityUpdatedMsg:
m.choosingPriority = false
m.priorityIssueID = ""
if msg.Err != nil {
return m, nil
}
return m, m.refreshIssueListsAndSelectIssue(msg.IssueID)
case selectIssueMsg: case selectIssueMsg:
m.issueList.SelectIssueID(msg.IssueID) m.issueList.SelectIssueID(msg.IssueID)
m.closedIssueList.SelectIssueID(msg.IssueID) m.closedIssueList.SelectIssueID(msg.IssueID)
@@ -256,6 +277,23 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
} }
} }
if m.choosingPriority {
switch msg.String() {
case "0", "1", "2", "3", "4":
issueID := m.priorityIssueID
priority := int(msg.String()[0] - '0')
m.choosingPriority = false
m.priorityIssueID = ""
return m, updateIssuePriorityCmd(m.svc, issueID, priority)
case "esc":
m.choosingPriority = false
m.priorityIssueID = ""
return m, nil
default:
return m, nil
}
}
if m.creatingIssue { if m.creatingIssue {
if msg.String() == "enter" { if msg.String() == "enter" {
title := m.createTitleInput.Value() title := m.createTitleInput.Value()

View File

@@ -7,6 +7,7 @@ import (
func (m *Model) View() string { func (m *Model) View() string {
if m.width == 0 || m.height == 0 { if m.width == 0 || m.height == 0 {
// if there is no space just print a loading message
return "Loading..." return "Loading..."
} }
@@ -125,6 +126,20 @@ func (m *Model) View() string {
return lipgloss.Place(m.width, m.height, lipgloss.Center, lipgloss.Center, statusBox) return lipgloss.Place(m.width, m.height, lipgloss.Center, lipgloss.Center, statusBox)
} }
if m.choosingPriority {
priorityContent := lipgloss.JoinVertical(lipgloss.Left,
styles.LabelStyle.Render("Change priority for "+m.priorityIssueID+":"),
lipgloss.NewStyle().Foreground(styles.FaintText).Render("0 = lowest 1 = low 2 = medium 3 = high 4 = highest"),
lipgloss.NewStyle().Foreground(styles.FaintText).Render("Esc = cancel"),
)
priorityBoxWidth := min(60, m.width-4)
priorityBox := styles.ContainerStyle.
Width(priorityBoxWidth).
BorderForeground(styles.PrimaryBorder).
Render(priorityContent)
return lipgloss.Place(m.width, m.height, lipgloss.Center, lipgloss.Center, priorityBox)
}
return mainView return mainView
} }