using codenames for priority levels mapped to digits

This commit is contained in:
viljarb
2026-02-24 11:19:01 +01:00
parent 970c735fa4
commit a8aada1646
7 changed files with 124 additions and 18 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/p") + " edit",
styles.HighlightKey("e/d/s/p/t") + " edit",
styles.HighlightKey("x") + " delete",
styles.HighlightKey("q") + " quit",
styles.HighlightKey("?") + " help",
@@ -76,8 +76,8 @@ func (h HelpBar) fullHelp() string {
renderRow("b", "back to list", "a", "add issue"),
renderRow("e", "edit title", "d", "edit desc"),
renderRow("s", "change status", "p", "change priority"),
renderRow("x", "delete issue", "?", "help"),
renderRow("q", "quit", "", ""),
renderRow("t", "change type", "x", "delete issue"),
renderRow("?", "help", "q", "quit"),
}
content := lipgloss.JoinVertical(lipgloss.Left, rows...)
return lipgloss.NewStyle().

View File

@@ -1,8 +1,6 @@
package dashboard
import (
"fmt"
"github.com/LazyBachelor/LazyPM/internal/models"
"github.com/LazyBachelor/LazyPM/pkg/tui/styles"
"github.com/charmbracelet/bubbles/viewport"
@@ -56,7 +54,7 @@ func (i *IssueDetail) refreshContent() {
)
priorityRow := styles.RowStyle.Render(
styles.LabelStyle.Render("Priority:") + styles.ValueStyle.Render(fmt.Sprintf("%d", i.issue.Priority)),
styles.LabelStyle.Render("Priority:") + styles.ValueStyle.Render(priorityCodeName(i.issue.Priority)),
)
descLabel := styles.LabelStyle.Render("Description:")

View File

@@ -158,8 +158,8 @@ func ClosedOnly(issues []models.Issue) []models.Issue {
return out
}
// sortByPriorityDesc sorts issues by priority, highest first.
func sortByPriorityDesc(issues []models.Issue) {
// sorts issues by priority, highest first.
sort.Slice(issues, func(i, j int) bool {
return issues[i].Priority > issues[j].Priority
})
@@ -313,6 +313,21 @@ func renderRow(issue ListIssue, isSelected bool, cols []TableColumn) string {
return lipgloss.JoinHorizontal(lipgloss.Left, parts...)
}
var priorityCodeNames = map[int]string{
0: "irrelevant",
1: "low",
2: "normal",
3: "high",
4: "critical",
}
func priorityCodeName(priority int) string {
if name, ok := priorityCodeNames[priority]; ok {
return name
}
return fmt.Sprintf("%d", priority)
}
func getColumnValue(col TableColumn, issue ListIssue) string {
switch col.key {
case "id":
@@ -324,7 +339,7 @@ func getColumnValue(col TableColumn, issue ListIssue) string {
case "type":
return string(issue.Issue.IssueType)
case "priority":
return fmt.Sprintf("%d", issue.Issue.Priority)
return priorityCodeName(issue.Issue.Priority)
default:
return ""
}

View File

@@ -17,6 +17,7 @@ type DashboardKeyMap struct {
EditDescription key.Binding
ChangeStatus key.Binding
ChangePriority key.Binding
ChangeType key.Binding
AddIssue key.Binding
DeleteIssue key.Binding
}
@@ -66,6 +67,10 @@ var defaultDashboardKeyMap = DashboardKeyMap{
key.WithKeys("p"),
key.WithHelp("p", "change priority"),
),
ChangeType: key.NewBinding(
key.WithKeys("t"),
key.WithHelp("t", "change type"),
),
AddIssue: key.NewBinding(
key.WithKeys("a"),
key.WithHelp("a", "add issue"),
@@ -94,28 +99,32 @@ 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.choosingPriority && !d.confirmingDelete && key.Matches(msg, d.keyMap.EditTitle):
case !d.editingTitle && !d.creatingIssue && !d.editingDescription && !d.choosingStatus && !d.choosingPriority && !d.confirmingDelete && !d.choosingType && 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.choosingPriority && !d.confirmingDelete && key.Matches(msg, d.keyMap.EditDescription):
case !d.editingTitle && !d.creatingIssue && !d.editingDescription && !d.choosingStatus && !d.choosingPriority && !d.confirmingDelete && !d.choosingType && 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.choosingPriority && !d.confirmingDelete && key.Matches(msg, d.keyMap.ChangeStatus):
case !d.editingTitle && !d.creatingIssue && !d.editingDescription && !d.choosingStatus && !d.choosingPriority && !d.confirmingDelete && !d.choosingType && 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.choosingPriority && !d.confirmingDelete && key.Matches(msg, d.keyMap.ChangePriority):
case !d.editingTitle && !d.creatingIssue && !d.editingDescription && !d.choosingStatus && !d.choosingPriority && !d.confirmingDelete && !d.choosingType && 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):
case !d.editingTitle && !d.creatingIssue && !d.editingDescription && !d.choosingStatus && !d.choosingPriority && !d.confirmingDelete && !d.choosingType && key.Matches(msg, d.keyMap.ChangeType):
if selected := d.FocusedIssueList().SelectedItem(); selected.ID != "" {
d.startChooseType(selected)
}
case !d.editingTitle && !d.creatingIssue && !d.editingDescription && !d.choosingStatus && !d.choosingPriority && !d.confirmingDelete && !d.choosingType && key.Matches(msg, d.keyMap.AddIssue):
d.startCreateIssue()
cmd = d.createTitleInput.Focus()
case !d.editingTitle && !d.creatingIssue && !d.editingDescription && !d.choosingStatus && !d.choosingPriority && !d.confirmingDelete && key.Matches(msg, d.keyMap.DeleteIssue):
case !d.editingTitle && !d.creatingIssue && !d.editingDescription && !d.choosingStatus && !d.choosingPriority && !d.confirmingDelete && !d.choosingType && key.Matches(msg, d.keyMap.DeleteIssue):
fl := d.FocusedIssueList()
if selected := fl.SelectedItem(); selected.ID != "" {
d.startConfirmDelete(selected.ID, fl.Index())

View File

@@ -41,10 +41,12 @@ type Model struct {
deleteConfirmID string
deleteConfirmIndex int
choosingStatus bool
choosingStatus bool // true while choosing a status
statusIssueID string
choosingPriority bool
priorityIssueID string
choosingPriority bool // true while choosing a priority
priorityIssueID string
choosingType bool // true while choosing a type
typeIssueID string
feedbackChan chan task.ValidationFeedback
quitChan chan bool
currentFeedback task.ValidationFeedback
@@ -132,6 +134,11 @@ func (m *Model) startChoosePriority(selected ListIssue) {
m.priorityIssueID = selected.ID
}
func (m *Model) startChooseType(selected ListIssue) {
m.choosingType = true
m.typeIssueID = selected.ID
}
func (m *Model) Init() tea.Cmd {
return m.listenForValidation()
}

View File

@@ -29,6 +29,11 @@ type issuePriorityUpdatedMsg struct {
Err error
}
type issueTypeUpdatedMsg struct {
IssueID string
Err error
}
type selectIssueMsg struct {
IssueID string
}
@@ -76,12 +81,21 @@ func updateIssuePriorityCmd(svc *service.Services, issueID string, priority int)
}
}
func updateIssueTypeCmd(svc *service.Services, issueID string, issueType models.IssueType) tea.Cmd {
return func() tea.Msg {
updates := map[string]interface{}{"issue_type": string(issueType)}
err := svc.Beads.UpdateIssue(context.Background(), issueID, updates, "tui")
return issueTypeUpdatedMsg{IssueID: issueID, Err: err}
}
}
func createIssueCmd(svc *service.Services, title string) tea.Cmd {
return func() tea.Msg {
issue := &models.Issue{
Title: title,
Status: models.StatusOpen,
IssueType: models.TypeTask,
Priority: 2,
}
err := svc.Beads.CreateIssue(context.Background(), issue, "tui")
return issueCreatedMsg{Issue: issue, Err: err}
@@ -150,6 +164,14 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
return m, m.refreshIssueListsAndSelectIssue(msg.IssueID)
case issueTypeUpdatedMsg:
m.choosingType = false
m.typeIssueID = ""
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)
@@ -294,6 +316,42 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
}
if m.choosingType {
switch msg.String() {
case "b":
issueID := m.typeIssueID
m.choosingType = false
m.typeIssueID = ""
return m, updateIssueTypeCmd(m.svc, issueID, models.TypeBug)
case "f":
issueID := m.typeIssueID
m.choosingType = false
m.typeIssueID = ""
return m, updateIssueTypeCmd(m.svc, issueID, models.TypeFeature)
case "t":
issueID := m.typeIssueID
m.choosingType = false
m.typeIssueID = ""
return m, updateIssueTypeCmd(m.svc, issueID, models.TypeTask)
case "e":
issueID := m.typeIssueID
m.choosingType = false
m.typeIssueID = ""
return m, updateIssueTypeCmd(m.svc, issueID, models.TypeEpic)
case "c":
issueID := m.typeIssueID
m.choosingType = false
m.typeIssueID = ""
return m, updateIssueTypeCmd(m.svc, issueID, models.TypeChore)
case "esc":
m.choosingType = false
m.typeIssueID = ""
return m, nil
default:
return m, nil
}
}
if m.creatingIssue {
if msg.String() == "enter" {
title := m.createTitleInput.Value()
@@ -356,6 +414,11 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return m, cmd
}
// On main dashboard, ESC does nothing; only q quits; like in lazybeads.
if msg.String() == "esc" {
return m, nil
}
cmd := m.handleKeyMsg(msg)
if cmd != nil {
return m, cmd

View File

@@ -129,7 +129,7 @@ func (m *Model) View() string {
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("0 = irrelevant 1 = low 2 = normal 3 = high 4 = critical"),
lipgloss.NewStyle().Foreground(styles.FaintText).Render("Esc = cancel"),
)
priorityBoxWidth := min(60, m.width-4)
@@ -140,6 +140,20 @@ func (m *Model) View() string {
return lipgloss.Place(m.width, m.height, lipgloss.Center, lipgloss.Center, priorityBox)
}
if m.choosingType {
typeContent := lipgloss.JoinVertical(lipgloss.Left,
styles.LabelStyle.Render("Change type for "+m.typeIssueID+":"),
lipgloss.NewStyle().Foreground(styles.FaintText).Render("b = bug f = feature t = task e = epic c = chore"),
lipgloss.NewStyle().Foreground(styles.FaintText).Render("Esc = cancel"),
)
typeBoxWidth := min(65, m.width-4)
typeBox := styles.ContainerStyle.
Width(typeBoxWidth).
BorderForeground(styles.PrimaryBorder).
Render(typeContent)
return lipgloss.Place(m.width, m.height, lipgloss.Center, lipgloss.Center, typeBox)
}
return mainView
}