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("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().

View File

@@ -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,
)

View File

@@ -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 ""
}

View File

@@ -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())

View File

@@ -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()
}

View File

@@ -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()

View File

@@ -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
}