From 970c735fa47b81043a5b596e038a80869a2ea1d3 Mon Sep 17 00:00:00 2001 From: viljarb Date: Mon, 23 Feb 2026 15:56:16 +0100 Subject: [PATCH] adding functionality for editing priority; adding priority to the list of displayed issues; sorting the list of issues by their priority --- pkg/tui/views/dashboard/help_bar.go | 7 +++-- pkg/tui/views/dashboard/issue_detail.go | 7 +++++ pkg/tui/views/dashboard/issue_list.go | 14 +++++++++ pkg/tui/views/dashboard/keys.go | 19 +++++++++---- pkg/tui/views/dashboard/model.go | 15 +++++++--- pkg/tui/views/dashboard/operations.go | 38 +++++++++++++++++++++++++ pkg/tui/views/dashboard/view.go | 15 ++++++++++ 7 files changed, 103 insertions(+), 12 deletions(-) diff --git a/pkg/tui/views/dashboard/help_bar.go b/pkg/tui/views/dashboard/help_bar.go index aa44393..ac43e75 100644 --- a/pkg/tui/views/dashboard/help_bar.go +++ b/pkg/tui/views/dashboard/help_bar.go @@ -36,7 +36,7 @@ func (h HelpBar) shortHelp() string { styles.HighlightKey("↓/j") + " down", styles.HighlightKey("pgup/pgdn") + " page", styles.HighlightKey("a") + " add", - styles.HighlightKey("e/d/s") + " edit", + styles.HighlightKey("e/d/s/p") + " edit", styles.HighlightKey("x") + " delete", styles.HighlightKey("q") + " quit", styles.HighlightKey("?") + " help", @@ -75,8 +75,9 @@ func (h HelpBar) fullHelp() string { renderRow("pgup", "page up", "pgdn", "page down"), renderRow("b", "back to list", "a", "add issue"), renderRow("e", "edit title", "d", "edit desc"), - renderRow("s", "change status", "x", "delete issue"), - renderRow("?", "help", "q", "quit"), + renderRow("s", "change status", "p", "change priority"), + renderRow("x", "delete issue", "?", "help"), + renderRow("q", "quit", "", ""), } content := lipgloss.JoinVertical(lipgloss.Left, rows...) return lipgloss.NewStyle(). diff --git a/pkg/tui/views/dashboard/issue_detail.go b/pkg/tui/views/dashboard/issue_detail.go index 8706863..49a452e 100644 --- a/pkg/tui/views/dashboard/issue_detail.go +++ b/pkg/tui/views/dashboard/issue_detail.go @@ -1,6 +1,8 @@ package dashboard import ( + "fmt" + "github.com/LazyBachelor/LazyPM/internal/models" "github.com/LazyBachelor/LazyPM/pkg/tui/styles" "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)), ) + priorityRow := styles.RowStyle.Render( + styles.LabelStyle.Render("Priority:") + styles.ValueStyle.Render(fmt.Sprintf("%d", i.issue.Priority)), + ) + descLabel := styles.LabelStyle.Render("Description:") descContent := styles.ValueStyle.Render(i.issue.Description) @@ -61,6 +67,7 @@ func (i *IssueDetail) refreshContent() { idRow, typeRow, statusRow, + priorityRow, descLabel, descContent, ) diff --git a/pkg/tui/views/dashboard/issue_list.go b/pkg/tui/views/dashboard/issue_list.go index 2ea4d80..7ec4648 100644 --- a/pkg/tui/views/dashboard/issue_list.go +++ b/pkg/tui/views/dashboard/issue_list.go @@ -2,7 +2,9 @@ package dashboard import ( "context" + "fmt" "io" + "sort" "github.com/LazyBachelor/LazyPM/internal/models" "github.com/LazyBachelor/LazyPM/internal/service" @@ -53,6 +55,7 @@ func getTableColumns(width int) []TableColumn { {width: 20, label: "TITLE", key: "title"}, {width: 15, label: "STATUS", key: "status"}, {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) } } + sortByPriorityDesc(out) return out } @@ -150,9 +154,17 @@ func ClosedOnly(issues []models.Issue) []models.Issue { out = append(out, issue) } } + sortByPriorityDesc(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) { var cmd tea.Cmd oldIndex := l.list.Index() @@ -311,6 +323,8 @@ func getColumnValue(col TableColumn, issue ListIssue) string { return string(issue.Issue.Status) case "type": return string(issue.Issue.IssueType) + case "priority": + return fmt.Sprintf("%d", issue.Issue.Priority) default: return "" } diff --git a/pkg/tui/views/dashboard/keys.go b/pkg/tui/views/dashboard/keys.go index 0d4ce01..4e35599 100644 --- a/pkg/tui/views/dashboard/keys.go +++ b/pkg/tui/views/dashboard/keys.go @@ -16,6 +16,7 @@ type DashboardKeyMap struct { EditTitle key.Binding EditDescription key.Binding ChangeStatus key.Binding + ChangePriority key.Binding AddIssue key.Binding DeleteIssue key.Binding } @@ -61,6 +62,10 @@ var defaultDashboardKeyMap = DashboardKeyMap{ key.WithKeys("s"), key.WithHelp("s", "change status"), ), + ChangePriority: key.NewBinding( + key.WithKeys("p"), + key.WithHelp("p", "change priority"), + ), AddIssue: key.NewBinding( key.WithKeys("a"), key.WithHelp("a", "add issue"), @@ -89,24 +94,28 @@ func (d *Model) handleKeyMsg(msg tea.KeyMsg) tea.Cmd { d.issueDetail.ScrollUp(1) case d.IsFocusedOnDetail() && key.Matches(msg, d.keyMap.ScrollDown): 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 != "" { d.startEditTitle(selected) 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 != "" { d.startEditDescription(selected) 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 != "" { 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() 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() if selected := fl.SelectedItem(); selected.ID != "" { d.startConfirmDelete(selected.ID, fl.Index()) diff --git a/pkg/tui/views/dashboard/model.go b/pkg/tui/views/dashboard/model.go index 6a925b6..93c1391 100644 --- a/pkg/tui/views/dashboard/model.go +++ b/pkg/tui/views/dashboard/model.go @@ -24,9 +24,9 @@ type Model struct { svc *service.Services width 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 - focusedPaneClosed int + focusedPaneClosed int editingTitle bool // true while we are editing a title titleInput textinput.Model editingIssueID string @@ -41,8 +41,10 @@ type Model struct { deleteConfirmID string deleteConfirmIndex int - choosingStatus bool - statusIssueID string + choosingStatus bool + statusIssueID string + choosingPriority bool + priorityIssueID string feedbackChan chan task.ValidationFeedback quitChan chan bool currentFeedback task.ValidationFeedback @@ -125,6 +127,11 @@ func (m *Model) startChooseStatus(selected ListIssue) { m.statusIssueID = selected.ID } +func (m *Model) startChoosePriority(selected ListIssue) { + m.choosingPriority = true + m.priorityIssueID = selected.ID +} + func (m *Model) Init() tea.Cmd { return m.listenForValidation() } diff --git a/pkg/tui/views/dashboard/operations.go b/pkg/tui/views/dashboard/operations.go index 0d691dc..e764571 100644 --- a/pkg/tui/views/dashboard/operations.go +++ b/pkg/tui/views/dashboard/operations.go @@ -24,6 +24,11 @@ type issueStatusUpdatedMsg struct { Err error } +type issuePriorityUpdatedMsg struct { + IssueID string + Err error +} + type selectIssueMsg struct { 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 { return func() tea.Msg { issue := &models.Issue{ @@ -129,6 +142,14 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } 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: m.issueList.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 msg.String() == "enter" { title := m.createTitleInput.Value() diff --git a/pkg/tui/views/dashboard/view.go b/pkg/tui/views/dashboard/view.go index ae170c6..fbdf37d 100644 --- a/pkg/tui/views/dashboard/view.go +++ b/pkg/tui/views/dashboard/view.go @@ -7,6 +7,7 @@ import ( func (m *Model) View() string { if m.width == 0 || m.height == 0 { + // if there is no space just print a loading message return "Loading..." } @@ -125,6 +126,20 @@ func (m *Model) View() string { 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 }