diff --git a/pkg/tui/views/dashboard/operations.go b/pkg/tui/views/dashboard/operations.go index 5f0c9be..b2d6e81 100644 --- a/pkg/tui/views/dashboard/operations.go +++ b/pkg/tui/views/dashboard/operations.go @@ -7,10 +7,9 @@ import ( "charm.land/bubbles/v2/list" tea "charm.land/bubbletea/v2" - "github.com/LazyBachelor/LazyPM/internal/app" "github.com/LazyBachelor/LazyPM/internal/models" "github.com/LazyBachelor/LazyPM/pkg/tui/components" - "github.com/LazyBachelor/LazyPM/pkg/tui/issues" + "github.com/LazyBachelor/LazyPM/pkg/tui/msgs" ) func defaultCommentAuthor() string { @@ -26,68 +25,15 @@ func defaultCommentAuthor() string { return "user" } -type issueTitleUpdatedMsg struct { - IssueID string - Err error -} - -type issueDescriptionUpdatedMsg struct { - IssueID string - Err error -} - -type issueStatusUpdatedMsg struct { - IssueID string - Err error -} - -type issuePriorityUpdatedMsg struct { - IssueID string - Err error -} - -type issueTypeUpdatedMsg struct { - IssueID string - Err error -} - -type selectIssueMsg struct { - IssueID string -} - -type issueCreatedMsg struct { - Issue *models.Issue - Err error -} - -type issueDeletedMsg struct { - IssueID string - Err error - PreviousIndex int -} - -type issueCommentAddedMsg struct { - IssueID string - Err error -} - -func addIssueCommentCmd(app *app.App, issueID, author, text string) tea.Cmd { - return func() tea.Msg { - _, err := app.Issues.AddIssueComment(context.Background(), issueID, author, text) - return issueCommentAddedMsg{IssueID: issueID, Err: err} - } -} - func (m *Model) refreshIssueListsAndSelectIssue(issueID string) tea.Cmd { /* update handler for issueTitleUpdatedMsg, issueDescriptionUpdatedMsg, and issueStatusUpdatedMsg to avoid using nearly identical code for refreshing the issue lists and updating the detail view - Fetch all issues, update both lists, set the detail view for the given issue, and return a command to select that issue. Returns nil if fetch fails. + Fetch all msgs, update both lists, set the detail view for the given issue, and return a command to select that issue. Returns nil if fetch fails. */ allIssues, err := m.app.Issues.SearchIssues(context.Background(), "", models.IssueFilter{}) if err != nil { return nil } - setItemsCmd := m.issueList.SetIssues(components.OpenAndInProgressOnly(allIssues)) - closedSetCmd := m.closedIssueList.SetIssues(components.ClosedOnly(allIssues)) + setItemsCmd := m.issueList.SetIssues(components.SortedIssues(allIssues)) for _, issue := range allIssues { if issue.ID == issueID { m.setDetailIssueWithComments(*issue) @@ -103,12 +49,12 @@ func (m *Model) refreshIssueListsAndSelectIssue(issueID string) tea.Cmd { } } - return tea.Sequence(setItemsCmd, closedSetCmd, func() tea.Msg { return issues.SelectIssueMsg{IssueID: issueID} }) + return tea.Sequence(setItemsCmd, func() tea.Msg { return msgs.SelectIssueMsg{IssueID: issueID} }) } func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { - case issues.TitleUpdatedMsg: + case msgs.TitleUpdatedMsg: m.editingTitle = false m.editingIssueID = "" m.titleInput.Blur() @@ -119,7 +65,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.logAction("tui updated issue title") return m, m.refreshIssueListsAndSelectIssue(msg.IssueID) - case issues.DescriptionUpdatedMsg: + case msgs.DescriptionUpdatedMsg: m.editingDescription = false m.editingDescIssueID = "" m.descriptionInput.Blur() @@ -130,7 +76,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.logAction("tui updated issue description") return m, m.refreshIssueListsAndSelectIssue(msg.IssueID) - case issues.StatusUpdatedMsg: + case msgs.StatusUpdatedMsg: m.choosingStatus = false m.statusIssueID = "" if msg.Err != nil { @@ -140,7 +86,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.logAction("tui updated issue status") return m, m.refreshIssueListsAndSelectIssue(msg.IssueID) - case issues.PriorityUpdatedMsg: + case msgs.PriorityUpdatedMsg: m.choosingPriority = false m.priorityIssueID = "" if msg.Err != nil { @@ -150,7 +96,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.logAction("tui updated issue priority") return m, m.refreshIssueListsAndSelectIssue(msg.IssueID) - case issues.TypeUpdatedMsg: + case msgs.TypeUpdatedMsg: m.choosingType = false m.typeIssueID = "" if msg.Err != nil { @@ -160,7 +106,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.logAction("tui updated issue type") return m, m.refreshIssueListsAndSelectIssue(msg.IssueID) - case issues.AssigneeUpdatedMsg: + case msgs.AssigneeUpdatedMsg: m.editingAssignee = false m.assigneeIssueID = "" m.assigneeInput.Blur() @@ -171,12 +117,12 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.logAction("tui updated issue assignee") return m, m.refreshIssueListsAndSelectIssue(msg.IssueID) - case issues.SelectIssueMsg: + case msgs.SelectIssueMsg: m.issueList.SelectIssueID(msg.IssueID) m.closedIssueList.SelectIssueID(msg.IssueID) return m, nil - case issues.CreatedMsg: + case msgs.CreatedMsg: m.creatingIssue = false m.createTitleInput.Blur() m.createTitleInput.Reset() @@ -188,8 +134,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { if err != nil { return m, nil } - setItemsCmd := m.issueList.SetIssues(components.OpenAndInProgressOnly(allIssues)) - closedSetCmd := m.closedIssueList.SetIssues(components.ClosedOnly(allIssues)) + setItemsCmd := m.issueList.SetIssues(components.SortedIssues(allIssues)) // Determine the created issue from the refreshed list to ensure all fields (like ID) are populated. selectedIssue := msg.Issue @@ -205,8 +150,8 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.setDetailIssueWithComments(*selectedIssue) m.logAction("tui created issue") - return m, tea.Sequence(setItemsCmd, closedSetCmd, func() tea.Msg { return issues.SelectIssueMsg{IssueID: selectedIssue.ID} }) - case issueCommentAddedMsg: + return m, tea.Sequence(setItemsCmd, func() tea.Msg { return msgs.SelectIssueMsg{IssueID: selectedIssue.ID} }) + case msgs.IssueCommentAddedMsg: m.addingComment = false m.commentIssueID = "" m.commentInput.Blur() @@ -215,7 +160,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, nil } return m, m.refreshIssueListsAndSelectIssue(msg.IssueID) - case issues.DeletedMsg: + case msgs.DeletedMsg: m.confirmingDelete = false m.deleteConfirmID = "" if msg.Err != nil { @@ -223,53 +168,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, nil } m.logAction("tui deleted issue") - allIssues, err := m.app.Issues.SearchIssues(context.Background(), "", models.IssueFilter{}) - if err != nil { - return m, nil - } - openIssues := components.OpenAndInProgressOnly(allIssues) - closedIssues := components.ClosedOnly(allIssues) - setItemsCmd := m.issueList.SetIssues(openIssues) - closedSetCmd := m.closedIssueList.SetIssues(closedIssues) - // If there are no issues at all, clear the detail view and return. - if len(openIssues) == 0 && len(closedIssues) == 0 { - m.setDetailIssueWithComments(models.Issue{}) - return m, tea.Sequence(setItemsCmd, closedSetCmd) - } - - // Determine which list to use for the next selection. - var targetIssues []*models.Issue - if m.focusedWindow == 0 { - targetIssues = openIssues - if len(targetIssues) == 0 && len(closedIssues) > 0 { - // The open list became empty; fall back to closed issues. - targetIssues = closedIssues - m.focusedWindow = 1 - } - } else { - targetIssues = closedIssues - if len(targetIssues) == 0 && len(openIssues) > 0 { - // The closed list became empty; fall back to open/in-progress issues. - targetIssues = openIssues - m.focusedWindow = 0 - } - } - - // Safety: if targetIssues is still empty here, just clear detail and return. - if len(targetIssues) == 0 { - m.setDetailIssueWithComments(models.Issue{}) - return m, tea.Sequence(setItemsCmd, closedSetCmd) - } - - newIndex := msg.PreviousIndex - if newIndex >= len(targetIssues) { - newIndex = len(targetIssues) - 1 - } - selectedIssue := targetIssues[newIndex] - m.setDetailIssueWithComments(*selectedIssue) - return m, tea.Sequence(setItemsCmd, closedSetCmd, func() tea.Msg { - return issues.SelectIssueMsg{IssueID: selectedIssue.ID} - }) + return m, m.refreshIssueListsAndSelectIssue(msg.IssueID) case tea.KeyPressMsg: if m.confirmingDelete { @@ -280,7 +179,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { idx := m.deleteConfirmIndex m.confirmingDelete = false m.deleteConfirmID = "" - return m, issues.DeleteIssueCmd(m.app, issueID, idx) + return m, msgs.DeleteIssueCmd(m.app, issueID, idx) case "n", "N", "esc": m.logAction("tui canceled issue deletion") m.confirmingDelete = false @@ -296,25 +195,25 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { issueID := m.statusIssueID m.choosingStatus = false m.statusIssueID = "" - return m, issues.UpdateIssueStatusCmd(m.app, issueID, string(models.StatusOpen)) + return m, msgs.UpdateIssueStatusCmd(m.app, issueID, string(models.StatusOpen)) case "i": m.logAction("tui selected issue status in_progress") issueID := m.statusIssueID m.choosingStatus = false m.statusIssueID = "" - return m, issues.UpdateIssueStatusCmd(m.app, issueID, string(models.StatusInProgress)) + return m, msgs.UpdateIssueStatusCmd(m.app, issueID, string(models.StatusInProgress)) case "b": m.logAction("tui selected issue status blocked") issueID := m.statusIssueID m.choosingStatus = false m.statusIssueID = "" - return m, issues.UpdateIssueStatusCmd(m.app, issueID, string(models.StatusBlocked)) + return m, msgs.UpdateIssueStatusCmd(m.app, issueID, string(models.StatusBlocked)) case "r": m.logAction("tui selected issue status ready_to_sprint") issueID := m.statusIssueID m.choosingStatus = false m.statusIssueID = "" - return m, issues.UpdateIssueStatusCmd(m.app, issueID, string(models.StatusReadyToSprint)) + return m, msgs.UpdateIssueStatusCmd(m.app, issueID, string(models.StatusReadyToSprint)) case "c": m.logAction("tui selected issue status closing") issueID := m.statusIssueID @@ -364,7 +263,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { issueID := m.closeReasonIssueID m.choosingCloseReason = false m.closeReasonIssueID = "" - return m, issues.CloseIssueCmd(m.app, issueID, reason) + return m, msgs.CloseIssueCmd(m.app, issueID, reason) } } @@ -378,7 +277,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.closingOtherReason = false m.closeReasonIssueID = "" m.closeReasonInput.Blur() - return m, issues.CloseIssueCmd(m.app, issueID, reason) + return m, msgs.CloseIssueCmd(m.app, issueID, reason) } case "esc": m.logAction("tui canceled custom close reason") @@ -400,7 +299,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { priority := int(msg.String()[0] - '0') m.choosingPriority = false m.priorityIssueID = "" - return m, issues.UpdateIssuePriorityCmd(m.app, issueID, priority) + return m, msgs.UpdateIssuePriorityCmd(m.app, issueID, priority) case "esc": m.logAction("tui canceled priority picker") m.choosingPriority = false @@ -418,31 +317,31 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { issueID := m.typeIssueID m.choosingType = false m.typeIssueID = "" - return m, issues.UpdateIssueTypeCmd(m.app, issueID, models.TypeBug) + return m, msgs.UpdateIssueTypeCmd(m.app, issueID, models.TypeBug) case "f": m.logAction("tui selected issue type feature") issueID := m.typeIssueID m.choosingType = false m.typeIssueID = "" - return m, issues.UpdateIssueTypeCmd(m.app, issueID, models.TypeFeature) + return m, msgs.UpdateIssueTypeCmd(m.app, issueID, models.TypeFeature) case "t": m.logAction("tui selected issue type task") issueID := m.typeIssueID m.choosingType = false m.typeIssueID = "" - return m, issues.UpdateIssueTypeCmd(m.app, issueID, models.TypeTask) + return m, msgs.UpdateIssueTypeCmd(m.app, issueID, models.TypeTask) case "e": m.logAction("tui selected issue type epic") issueID := m.typeIssueID m.choosingType = false m.typeIssueID = "" - return m, issues.UpdateIssueTypeCmd(m.app, issueID, models.TypeEpic) + return m, msgs.UpdateIssueTypeCmd(m.app, issueID, models.TypeEpic) case "c": m.logAction("tui selected issue type chore") issueID := m.typeIssueID m.choosingType = false m.typeIssueID = "" - return m, issues.UpdateIssueTypeCmd(m.app, issueID, models.TypeChore) + return m, msgs.UpdateIssueTypeCmd(m.app, issueID, models.TypeChore) case "esc": m.logAction("tui canceled type picker") m.choosingType = false @@ -458,7 +357,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { title := m.createTitleInput.Value() if title != "" { m.logAction("tui submitted new issue") - return m, issues.CreateIssueCmd(m.app, title) + return m, msgs.CreateIssueCmd(m.app, title) } } if msg.String() == "esc" { @@ -477,7 +376,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { if msg.String() == "enter" { assignee := m.assigneeInput.Value() m.logAction("tui submitted assignee edit") - return m, issues.UpdateIssueAssigneeCmd(m.app, m.assigneeIssueID, assignee) + return m, msgs.UpdateIssueAssigneeCmd(m.app, m.assigneeIssueID, assignee) } if msg.String() == "esc" { m.logAction("tui canceled assignee edit") @@ -496,7 +395,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { newTitle := m.titleInput.Value() if newTitle != "" { m.logAction("tui submitted issue title edit") - return m, issues.UpdateIssueTitleCmd(m.app, m.editingIssueID, newTitle) + return m, msgs.UpdateIssueTitleCmd(m.app, m.editingIssueID, newTitle) } } if msg.String() == "esc" { @@ -520,7 +419,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.commentIssueID = "" m.commentInput.Blur() m.commentInput.Reset() - return m, addIssueCommentCmd(m.app, issueID, defaultCommentAuthor(), text) + return m, msgs.AddIssueCommentCmd(m.app, issueID, defaultCommentAuthor(), text) } } if msg.String() == "esc" { @@ -543,7 +442,7 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.editingDescription = false m.editingDescIssueID = "" m.descriptionInput.Blur() - return m, issues.UpdateIssueDescriptionCmd(m.app, issueID, newDesc) + return m, msgs.UpdateIssueDescriptionCmd(m.app, issueID, newDesc) } if msg.String() == "esc" { m.logAction("tui canceled issue description edit") @@ -557,9 +456,8 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, cmd } - focusedList := m.FocusedIssueList() - if focusedList.FilterState() == list.Filtering { - cmd, _ := focusedList.Update(msg) + if m.issueList.FilterState() == list.Filtering { + cmd, _ := m.issueList.Update(msg) return m, cmd }