adding blocked status to tui
This commit is contained in:
@@ -70,7 +70,7 @@ func (d *Model) handleKeyMsg(msg tea.KeyMsg) tea.Cmd {
|
||||
d.updateDetailFromSelection()
|
||||
}
|
||||
case !d.IsInModal() && key.Matches(msg, d.keyMap.MoveColumnRight):
|
||||
if d.focusedColumn < 2 {
|
||||
if d.focusedColumn < 3 {
|
||||
d.focusedColumn++
|
||||
d.updateDetailFromSelection()
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ type Model struct {
|
||||
header Header
|
||||
todoList IssueList
|
||||
inProgList IssueList
|
||||
blockedList IssueList
|
||||
doneList IssueList
|
||||
issueDetail IssueDetail
|
||||
helpBar components.HelpBar
|
||||
@@ -30,7 +31,7 @@ type Model struct {
|
||||
width int
|
||||
height int
|
||||
|
||||
focusedColumn int // 0 = To Do, 1 = In Progress, 2 = Done
|
||||
focusedColumn int // 0 = To Do, 1 = In Progress, 2 = Blocked, 3 = Done
|
||||
focusOnDetail bool // true when detail pane is focused
|
||||
|
||||
editingTitle bool // true while we are editing a title
|
||||
@@ -80,10 +81,12 @@ func NewDashboard(app *app.App, feedbackChan chan models.ValidationFeedback, qui
|
||||
allIssues, _ := app.Issues.SearchIssues(context.Background(), "", models.IssueFilter{})
|
||||
todoIssues := components.StatusOnly(allIssues, models.StatusOpen)
|
||||
inProgIssues := components.StatusOnly(allIssues, models.StatusInProgress)
|
||||
blockedIssues := components.StatusOnly(allIssues, models.StatusBlocked)
|
||||
doneIssues := components.StatusOnly(allIssues, models.StatusClosed)
|
||||
|
||||
m.todoList = components.NewIssueListFromIssues(app, todoIssues, 0, 0)
|
||||
m.inProgList = components.NewIssueListFromIssues(app, inProgIssues, 0, 0)
|
||||
m.blockedList = components.NewIssueListFromIssues(app, blockedIssues, 0, 0)
|
||||
m.doneList = components.NewIssueListFromIssues(app, doneIssues, 0, 0)
|
||||
m.issueDetail = components.NewIssueDetail()
|
||||
m.helpBar = components.NewHelpBar(components.ViewKanban)
|
||||
@@ -98,6 +101,8 @@ func NewDashboard(app *app.App, feedbackChan chan models.ValidationFeedback, qui
|
||||
m.issueDetail.SetIssue(selected.Issue)
|
||||
} else if selected := m.inProgList.SelectedItem(); selected.ID != "" {
|
||||
m.issueDetail.SetIssue(selected.Issue)
|
||||
} else if selected := m.blockedList.SelectedItem(); selected.ID != "" {
|
||||
m.issueDetail.SetIssue(selected.Issue)
|
||||
} else if selected := m.doneList.SelectedItem(); selected.ID != "" {
|
||||
m.issueDetail.SetIssue(selected.Issue)
|
||||
}
|
||||
@@ -196,6 +201,8 @@ func (m *Model) FocusedIssueList() *IssueList {
|
||||
case 1:
|
||||
return &m.inProgList
|
||||
case 2:
|
||||
return &m.blockedList
|
||||
case 3:
|
||||
return &m.doneList
|
||||
default:
|
||||
return &m.todoList
|
||||
@@ -219,6 +226,8 @@ func statusForColumn(col int) models.Status {
|
||||
case 1:
|
||||
return models.StatusInProgress
|
||||
case 2:
|
||||
return models.StatusBlocked
|
||||
case 3:
|
||||
return models.StatusClosed
|
||||
default:
|
||||
return models.StatusOpen
|
||||
@@ -235,7 +244,7 @@ func (m *Model) moveIssue(delta int) tea.Cmd {
|
||||
}
|
||||
|
||||
newCol := m.focusedColumn + delta
|
||||
if newCol < 0 || newCol > 2 {
|
||||
if newCol < 0 || newCol > 3 {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -21,10 +21,12 @@ func (m *Model) refreshIssueListsAndSelectIssue(issueID string) tea.Cmd {
|
||||
|
||||
todoIssues := components.StatusOnly(allIssues, models.StatusOpen)
|
||||
inProgIssues := components.StatusOnly(allIssues, models.StatusInProgress)
|
||||
blockedIssues := components.StatusOnly(allIssues, models.StatusBlocked)
|
||||
doneIssues := components.StatusOnly(allIssues, models.StatusClosed)
|
||||
|
||||
todoCmd := m.todoList.SetIssues(todoIssues)
|
||||
inProgCmd := m.inProgList.SetIssues(inProgIssues)
|
||||
blockedCmd := m.blockedList.SetIssues(blockedIssues)
|
||||
doneCmd := m.doneList.SetIssues(doneIssues)
|
||||
|
||||
var targetStatus models.Status
|
||||
@@ -41,16 +43,19 @@ func (m *Model) refreshIssueListsAndSelectIssue(issueID string) tea.Cmd {
|
||||
m.focusedColumn = 0
|
||||
case models.StatusInProgress:
|
||||
m.focusedColumn = 1
|
||||
case models.StatusClosed:
|
||||
case models.StatusBlocked:
|
||||
m.focusedColumn = 2
|
||||
case models.StatusClosed:
|
||||
m.focusedColumn = 3
|
||||
}
|
||||
|
||||
// Select the moved issue in its new column immediately so the highlight follows it.
|
||||
m.todoList.SelectIssueID(issueID)
|
||||
m.inProgList.SelectIssueID(issueID)
|
||||
m.blockedList.SelectIssueID(issueID)
|
||||
m.doneList.SelectIssueID(issueID)
|
||||
|
||||
return tea.Sequence(todoCmd, inProgCmd, doneCmd)
|
||||
return tea.Sequence(todoCmd, inProgCmd, blockedCmd, doneCmd)
|
||||
}
|
||||
|
||||
func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
@@ -109,6 +114,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
case issues.SelectIssueMsg:
|
||||
m.todoList.SelectIssueID(msg.IssueID)
|
||||
m.inProgList.SelectIssueID(msg.IssueID)
|
||||
m.blockedList.SelectIssueID(msg.IssueID)
|
||||
m.doneList.SelectIssueID(msg.IssueID)
|
||||
return m, nil
|
||||
|
||||
@@ -126,10 +132,12 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
|
||||
todoIssues := components.StatusOnly(allIssues, models.StatusOpen)
|
||||
inProgIssues := components.StatusOnly(allIssues, models.StatusInProgress)
|
||||
blockedIssues := components.StatusOnly(allIssues, models.StatusBlocked)
|
||||
doneIssues := components.StatusOnly(allIssues, models.StatusClosed)
|
||||
|
||||
todoCmd := m.todoList.SetIssues(todoIssues)
|
||||
inProgCmd := m.inProgList.SetIssues(inProgIssues)
|
||||
blockedCmd := m.blockedList.SetIssues(blockedIssues)
|
||||
doneCmd := m.doneList.SetIssues(doneIssues)
|
||||
|
||||
// Determine the created issue from the refreshed list to ensure all fields (like ID) are populated.
|
||||
@@ -145,7 +153,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
}
|
||||
|
||||
m.issueDetail.SetIssue(*selectedIssue)
|
||||
return m, tea.Sequence(todoCmd, inProgCmd, doneCmd, func() tea.Msg { return issues.SelectIssueMsg{IssueID: selectedIssue.ID} })
|
||||
return m, tea.Sequence(todoCmd, inProgCmd, blockedCmd, doneCmd, func() tea.Msg { return issues.SelectIssueMsg{IssueID: selectedIssue.ID} })
|
||||
case issues.DeletedMsg:
|
||||
m.confirmingDelete = false
|
||||
m.deleteConfirmID = ""
|
||||
@@ -159,16 +167,18 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
|
||||
todoIssues := components.StatusOnly(allIssues, models.StatusOpen)
|
||||
inProgIssues := components.StatusOnly(allIssues, models.StatusInProgress)
|
||||
blockedIssues := components.StatusOnly(allIssues, models.StatusBlocked)
|
||||
doneIssues := components.StatusOnly(allIssues, models.StatusClosed)
|
||||
|
||||
todoCmd := m.todoList.SetIssues(todoIssues)
|
||||
inProgCmd := m.inProgList.SetIssues(inProgIssues)
|
||||
blockedCmd := m.blockedList.SetIssues(blockedIssues)
|
||||
doneCmd := m.doneList.SetIssues(doneIssues)
|
||||
|
||||
// If there are no issues at all, clear the detail view and return.
|
||||
if len(todoIssues) == 0 && len(inProgIssues) == 0 && len(doneIssues) == 0 {
|
||||
if len(todoIssues) == 0 && len(inProgIssues) == 0 && len(blockedIssues) == 0 && len(doneIssues) == 0 {
|
||||
m.issueDetail.SetIssue(models.Issue{})
|
||||
return m, tea.Sequence(todoCmd, inProgCmd, doneCmd)
|
||||
return m, tea.Sequence(todoCmd, inProgCmd, blockedCmd, doneCmd)
|
||||
}
|
||||
|
||||
// Determine which column to use for the next selection based on the current focus.
|
||||
@@ -180,9 +190,12 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
if len(inProgIssues) > 0 {
|
||||
targetIssues = inProgIssues
|
||||
m.focusedColumn = 1
|
||||
} else if len(blockedIssues) > 0 {
|
||||
targetIssues = blockedIssues
|
||||
m.focusedColumn = 2
|
||||
} else if len(doneIssues) > 0 {
|
||||
targetIssues = doneIssues
|
||||
m.focusedColumn = 2
|
||||
m.focusedColumn = 3
|
||||
}
|
||||
}
|
||||
case 1:
|
||||
@@ -191,13 +204,16 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
if len(todoIssues) > 0 {
|
||||
targetIssues = todoIssues
|
||||
m.focusedColumn = 0
|
||||
} else if len(blockedIssues) > 0 {
|
||||
targetIssues = blockedIssues
|
||||
m.focusedColumn = 2
|
||||
} else if len(doneIssues) > 0 {
|
||||
targetIssues = doneIssues
|
||||
m.focusedColumn = 2
|
||||
m.focusedColumn = 3
|
||||
}
|
||||
}
|
||||
case 2:
|
||||
targetIssues = doneIssues
|
||||
targetIssues = blockedIssues
|
||||
if len(targetIssues) == 0 {
|
||||
if len(inProgIssues) > 0 {
|
||||
targetIssues = inProgIssues
|
||||
@@ -205,6 +221,23 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
} else if len(todoIssues) > 0 {
|
||||
targetIssues = todoIssues
|
||||
m.focusedColumn = 0
|
||||
} else if len(doneIssues) > 0 {
|
||||
targetIssues = doneIssues
|
||||
m.focusedColumn = 3
|
||||
}
|
||||
}
|
||||
case 3:
|
||||
targetIssues = doneIssues
|
||||
if len(targetIssues) == 0 {
|
||||
if len(blockedIssues) > 0 {
|
||||
targetIssues = blockedIssues
|
||||
m.focusedColumn = 2
|
||||
} else if len(inProgIssues) > 0 {
|
||||
targetIssues = inProgIssues
|
||||
m.focusedColumn = 1
|
||||
} else if len(todoIssues) > 0 {
|
||||
targetIssues = todoIssues
|
||||
m.focusedColumn = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -212,7 +245,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
// Safety: if targetIssues is still empty here, just clear detail and return.
|
||||
if len(targetIssues) == 0 {
|
||||
m.issueDetail.SetIssue(models.Issue{})
|
||||
return m, tea.Sequence(todoCmd, inProgCmd, doneCmd)
|
||||
return m, tea.Sequence(todoCmd, inProgCmd, blockedCmd, doneCmd)
|
||||
}
|
||||
|
||||
newIndex := msg.PreviousIndex
|
||||
@@ -221,7 +254,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
}
|
||||
selectedIssue := targetIssues[newIndex]
|
||||
m.issueDetail.SetIssue(*selectedIssue)
|
||||
return m, tea.Sequence(todoCmd, inProgCmd, doneCmd, func() tea.Msg {
|
||||
return m, tea.Sequence(todoCmd, inProgCmd, blockedCmd, doneCmd, func() tea.Msg {
|
||||
return issues.SelectIssueMsg{IssueID: selectedIssue.ID}
|
||||
})
|
||||
|
||||
@@ -253,6 +286,11 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
m.choosingStatus = false
|
||||
m.statusIssueID = ""
|
||||
return m, issues.UpdateIssueStatusCmd(m.app, issueID, string(models.StatusInProgress))
|
||||
case "b":
|
||||
issueID := m.statusIssueID
|
||||
m.choosingStatus = false
|
||||
m.statusIssueID = ""
|
||||
return m, issues.UpdateIssueStatusCmd(m.app, issueID, string(models.StatusBlocked))
|
||||
case "r":
|
||||
issueID := m.statusIssueID
|
||||
m.choosingStatus = false
|
||||
|
||||
@@ -22,7 +22,7 @@ func (m *Model) View() string {
|
||||
|
||||
contentHeight := m.height - headerHeight - footerHeight
|
||||
totalContentWidth := m.width - 1
|
||||
colWidth := totalContentWidth / 3
|
||||
colWidth := totalContentWidth / 4
|
||||
if colWidth < 20 {
|
||||
colWidth = 20
|
||||
}
|
||||
@@ -35,18 +35,21 @@ func (m *Model) View() string {
|
||||
|
||||
m.todoList.SetSize(colWidth, boardHeight-1)
|
||||
m.inProgList.SetSize(colWidth, boardHeight-1)
|
||||
m.blockedList.SetSize(colWidth, boardHeight-1)
|
||||
m.doneList.SetSize(colWidth, boardHeight-1)
|
||||
|
||||
// Only highlight the selected row in the focused column.
|
||||
m.todoList.SetHighlightSelected(!m.focusOnDetail && m.focusedColumn == 0)
|
||||
m.inProgList.SetHighlightSelected(!m.focusOnDetail && m.focusedColumn == 1)
|
||||
m.doneList.SetHighlightSelected(!m.focusOnDetail && m.focusedColumn == 2)
|
||||
m.blockedList.SetHighlightSelected(!m.focusOnDetail && m.focusedColumn == 2)
|
||||
m.doneList.SetHighlightSelected(!m.focusOnDetail && m.focusedColumn == 3)
|
||||
|
||||
// Detail view takes full width below the board.
|
||||
m.issueDetail.SetSize(totalContentWidth, contentHeight-boardHeight)
|
||||
|
||||
todoLabel := styles.LabelStyle.Render("To Do")
|
||||
inProgLabel := styles.LabelStyle.Render("In Progress")
|
||||
blockedLabel := styles.LabelStyle.Render("Blocked")
|
||||
doneLabel := styles.LabelStyle.Render("Done")
|
||||
|
||||
highlight := lipgloss.NewStyle().Foreground(styles.Primary).Bold(true)
|
||||
@@ -56,14 +59,17 @@ func (m *Model) View() string {
|
||||
case 1:
|
||||
inProgLabel = highlight.Render("In Progress ▶")
|
||||
case 2:
|
||||
blockedLabel = highlight.Render("Blocked ▶")
|
||||
case 3:
|
||||
doneLabel = highlight.Render("Done ▶")
|
||||
}
|
||||
|
||||
todoCol := lipgloss.JoinVertical(lipgloss.Left, todoLabel, m.todoList.View())
|
||||
inProgCol := lipgloss.JoinVertical(lipgloss.Left, inProgLabel, m.inProgList.View())
|
||||
blockedCol := lipgloss.JoinVertical(lipgloss.Left, blockedLabel, m.blockedList.View())
|
||||
doneCol := lipgloss.JoinVertical(lipgloss.Left, doneLabel, m.doneList.View())
|
||||
|
||||
board := lipgloss.JoinHorizontal(lipgloss.Left, todoCol, inProgCol, doneCol)
|
||||
board := lipgloss.JoinHorizontal(lipgloss.Left, todoCol, inProgCol, blockedCol, doneCol)
|
||||
content := lipgloss.JoinVertical(lipgloss.Left, board, m.issueDetail.View())
|
||||
|
||||
// Add spacer to lock footer to bottom of screen when content is shorter than available space
|
||||
|
||||
Reference in New Issue
Block a user