package dashboard import ( "context" "charm.land/bubbles/v2/textarea" "charm.land/bubbles/v2/textinput" tea "charm.land/bubbletea/v2" "github.com/LazyBachelor/LazyPM/internal/app" "github.com/LazyBachelor/LazyPM/internal/models" "github.com/LazyBachelor/LazyPM/pkg/tui/components" ) // Use shared types from components for consistency. type ( Header = components.Header IssueList = components.IssueList IssueDetail = components.IssueDetail ListIssue = components.ListIssue ) type Model struct { header Header issueList IssueList issueDetail IssueDetail closedIssueList IssueList helpBar components.HelpBar keyMap DashboardKeyMap app *app.App width int height int focusedWindow int // 0 = main (display issues), 1 = closed issues focusedPaneMain int // 0 = list, 1 = detail focusedPaneClosed int editingTitle bool // true while we are editing a title titleInput textinput.Model editingIssueID string editingDescription bool // true while editing a description descriptionInput textarea.Model editingDescIssueID string creatingIssue bool // true while creating a new issue createTitleInput textinput.Model confirmingDelete bool // true while confirming a delete deleteConfirmID string deleteConfirmIndex int choosingStatus bool statusIssueID string choosingPriority bool priorityIssueID string choosingType bool typeIssueID string editingAssignee bool assigneeInput textinput.Model assigneeIssueID string addingComment bool commentInput textarea.Model commentIssueID string choosingCloseReason bool closeReasonIssueID string closingOtherReason bool closeReasonInput textarea.Model feedbackChan chan models.ValidationFeedback quitChan chan bool currentFeedback models.ValidationFeedback showComplete bool submitChan chan<- struct{} } func NewDashboard(app *app.App, feedbackChan chan models.ValidationFeedback, quitChan chan bool, submitChan chan<- struct{}) *Model { m := &Model{ header: components.NewHeader("Project Manager Dashboard"), keyMap: defaultDashboardKeyMap, app: app, width: 80, height: 24, focusedWindow: 0, focusedPaneMain: 0, focusedPaneClosed: 0, feedbackChan: feedbackChan, quitChan: quitChan, submitChan: submitChan, } allIssues, _ := app.Issues.SearchIssues(context.Background(), "", models.IssueFilter{}) m.issueList = components.NewIssueListFromIssues(app, components.SortedIssues(allIssues), 0, 0) m.issueDetail = components.NewIssueDetail() m.helpBar = components.NewHelpBar(components.ViewIssues) inputs := components.NewIssueInputs() m.titleInput = inputs.Title m.createTitleInput = inputs.CreateTitle m.descriptionInput = inputs.Description m.assigneeInput = inputs.Assignee closeReasonTa := textarea.New() closeReasonTa.Placeholder = "Enter closing reason..." closeReasonTa.SetWidth(56) closeReasonTa.SetHeight(4) m.closeReasonInput = closeReasonTa commentTa := textarea.New() commentTa.Placeholder = "Write your comment..." commentTa.SetWidth(56) commentTa.SetHeight(6) m.commentInput = commentTa if selected := m.issueList.SelectedItem(); selected.ID != "" { m.setDetailIssueWithComments(selected.Issue) } else if selected := m.closedIssueList.SelectedItem(); selected.ID != "" { m.setDetailIssueWithComments(selected.Issue) } return m } // setDetailIssueWithComments sets the issue in the detail pane and loads its comments. func (m *Model) setDetailIssueWithComments(issue models.Issue) { m.issueDetail.SetIssue(issue) if issue.ID == "" { m.issueDetail.SetComments(nil) return } comments, _ := m.app.Issues.GetIssueComments(context.Background(), issue.ID) m.issueDetail.SetComments(comments) } func (m *Model) startAddComment(selected ListIssue) { m.addingComment = true m.commentIssueID = selected.ID m.commentInput.SetValue("") m.commentInput.Reset() } func (m *Model) logAction(action string) { if m.app != nil { m.app.LogAction(models.EncodeActionEvent(models.ActionEvent{ Source: "tui", Action: action, })) } } // submitValidation sends a validation request to the submit channel. // Call this after every successful user action that modifies issues. func (m *Model) submitValidation() { if m.submitChan != nil { select { case m.submitChan <- struct{}{}: m.logAction("tui submitted validation") default: } } } func (m *Model) startEditTitle(selected ListIssue) { m.editingTitle = true m.editingIssueID = selected.ID m.titleInput.SetValue(selected.Issue.Title) m.titleInput.CursorEnd() } func (m *Model) startEditDescription(selected ListIssue) { m.editingDescription = true m.editingDescIssueID = selected.ID m.descriptionInput.SetValue(selected.Issue.Description) m.descriptionInput.CursorEnd() } func (m *Model) startCreateIssue() { m.creatingIssue = true m.createTitleInput.SetValue("") m.createTitleInput.Reset() } func (m *Model) startConfirmDelete(issueID string, index int) { m.confirmingDelete = true m.deleteConfirmID = issueID m.deleteConfirmIndex = index } func (m *Model) startChooseStatus(selected ListIssue) { m.choosingStatus = true m.statusIssueID = selected.ID } func (m *Model) startChoosePriority(selected ListIssue) { m.choosingPriority = true m.priorityIssueID = selected.ID } func (m *Model) startChooseType(selected ListIssue) { m.choosingType = true m.typeIssueID = selected.ID } func (m *Model) startEditAssignee(selected ListIssue) { m.editingAssignee = true m.assigneeIssueID = selected.ID m.assigneeInput.SetValue(selected.Assignee) m.assigneeInput.CursorEnd() } func (m *Model) Init() tea.Cmd { if m.submitChan != nil { m.submitChan <- struct{}{} m.logAction("tui submitted validation") } return components.ListenForValidation(m.feedbackChan) } // IsInModal returns true when a modal (edit, create, delete confirm, choose status/priority/type) is active. func (m *Model) IsInModal() bool { return m.editingTitle || m.creatingIssue || m.editingDescription || m.choosingStatus || m.choosingPriority || m.confirmingDelete || m.choosingType || m.editingAssignee || m.choosingCloseReason || m.closingOtherReason } func (m *Model) IsFocusedOnList() bool { if m.focusedWindow == 0 { return m.focusedPaneMain == 0 } return m.focusedPaneClosed == 0 } func (m *Model) IsFocusedOnDetail() bool { if m.focusedWindow == 0 { return m.focusedPaneMain == 1 } return m.focusedPaneClosed == 1 }