Add sorting, user assignment, and UI improvements
Features: - Add work item sorting by ID, Type, State (keys 1, 2, 3) - Add user assignment modal with team member filtering (key a) - Add parent work item title display in details panel - Preserve sort order and selection on panel reload UI improvements: - Remove zebra striping from work items list - Remove priority column from list and details - Align metadata fields in details panel - Add markdown rendering for descriptions (using glamour) - Add state colors: To Do (orange), In Progress (purple), Done (green), Testing (yellow) 🤖 Generated with [Claude Code](https://claude.ai/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
+62
-1
@@ -42,6 +42,7 @@ type App struct {
|
||||
helpPanel components.HelpPanel
|
||||
stateModal components.StateModal
|
||||
branchModal components.BranchModal
|
||||
assignModal components.AssignModal
|
||||
|
||||
// State
|
||||
activePanel Panel
|
||||
@@ -55,6 +56,7 @@ type App struct {
|
||||
areas []models.Area
|
||||
workItems []models.WorkItem
|
||||
statesByType map[string][]models.WorkItemStateInfo
|
||||
teamMembers []models.TeamMember
|
||||
|
||||
// Services
|
||||
client *api.Client
|
||||
@@ -84,6 +86,7 @@ func NewApp(client *api.Client) App {
|
||||
helpPanel: components.NewHelpPanel(keys, styles),
|
||||
stateModal: components.NewStateModal(styles, keys),
|
||||
branchModal: components.NewBranchModal(styles, keys),
|
||||
assignModal: components.NewAssignModal(styles, keys),
|
||||
activePanel: PanelWorkItems,
|
||||
viewMode: ViewMain,
|
||||
loading: true,
|
||||
@@ -130,6 +133,15 @@ func (a App) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
return a, tea.Batch(cmds...)
|
||||
}
|
||||
|
||||
if a.assignModal.IsVisible() {
|
||||
newModal, cmd := a.assignModal.Update(msg)
|
||||
a.assignModal = newModal
|
||||
if cmd != nil {
|
||||
cmds = append(cmds, cmd)
|
||||
}
|
||||
return a, tea.Batch(cmds...)
|
||||
}
|
||||
|
||||
// Global keys
|
||||
if key.Matches(msg, a.keys.Quit) && !a.helpPanel.IsVisible() && a.viewMode == ViewMain {
|
||||
return a, tea.Quit
|
||||
@@ -195,6 +207,17 @@ func (a App) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
}
|
||||
}
|
||||
|
||||
// Open assign modal (only when work items panel is active)
|
||||
if key.Matches(msg, a.keys.Assign) && a.activePanel == PanelWorkItems {
|
||||
if item := a.workItemsPanel.SelectedItem(); item != nil {
|
||||
a.assignModal.SetItem(item)
|
||||
a.assignModal.SetMembers(a.teamMembers)
|
||||
a.assignModal.SetSize(a.width, a.height)
|
||||
a.assignModal.SetVisible(true)
|
||||
return a, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Update active panel
|
||||
switch a.activePanel {
|
||||
case PanelFilter:
|
||||
@@ -215,6 +238,7 @@ func (a App) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
a.iterations = msg.iterations
|
||||
a.areas = msg.areas
|
||||
a.statesByType = msg.statesByType
|
||||
a.teamMembers = msg.teamMembers
|
||||
a.stateModal.SetStatesByType(a.statesByType)
|
||||
filterState := models.NewFilterState(a.iterations, a.areas, a.statesByType)
|
||||
|
||||
@@ -268,6 +292,7 @@ func (a App) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
// Modal was closed, nothing special to do
|
||||
a.stateModal.SetVisible(false)
|
||||
a.branchModal.SetVisible(false)
|
||||
a.assignModal.SetVisible(false)
|
||||
|
||||
case components.StateChangeRequestMsg:
|
||||
a.stateModal.SetVisible(false)
|
||||
@@ -289,6 +314,17 @@ func (a App) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
|
||||
case components.BranchCreateErrorMsg:
|
||||
a.err = msg.Err
|
||||
|
||||
case components.AssignRequestMsg:
|
||||
a.assignModal.SetVisible(false)
|
||||
a.loading = true
|
||||
return a, assignWorkItemCmd(a.client, msg.Item.ID, msg.UserEmail, msg.UserName, a.filterPanel.FilterState())
|
||||
|
||||
case assignedMsg:
|
||||
a.loading = false
|
||||
a.statusMsg = fmt.Sprintf("Assigned to %s", msg.userName)
|
||||
// Refresh work items to show updated assignment
|
||||
return a, loadWorkItemsCmd(a.client, a.filterPanel.FilterState())
|
||||
}
|
||||
|
||||
// Update selected item in details panel
|
||||
@@ -313,6 +349,11 @@ func (a App) View() string {
|
||||
return a.branchModal.View()
|
||||
}
|
||||
|
||||
// Render assign modal if visible
|
||||
if a.assignModal.IsVisible() {
|
||||
return a.assignModal.View()
|
||||
}
|
||||
|
||||
// Render help overlay if visible
|
||||
if a.helpPanel.IsVisible() {
|
||||
_ = a.renderMainView()
|
||||
@@ -455,6 +496,7 @@ type dataLoadedMsg struct {
|
||||
iterations []models.Iteration
|
||||
areas []models.Area
|
||||
statesByType map[string][]models.WorkItemStateInfo
|
||||
teamMembers []models.TeamMember
|
||||
}
|
||||
|
||||
type workItemsLoadedMsg struct {
|
||||
@@ -469,6 +511,10 @@ type stateChangedMsg struct {
|
||||
newState string
|
||||
}
|
||||
|
||||
type assignedMsg struct {
|
||||
userName string
|
||||
}
|
||||
|
||||
// Commands
|
||||
|
||||
func loadDataCmd(client *api.Client) tea.Cmd {
|
||||
@@ -486,7 +532,12 @@ func loadDataCmd(client *api.Client) tea.Cmd {
|
||||
// Non-fatal - we can still work with hardcoded states
|
||||
statesByType = make(map[string][]models.WorkItemStateInfo)
|
||||
}
|
||||
return dataLoadedMsg{iterations: iterations, areas: areas, statesByType: statesByType}
|
||||
teamMembers, err := client.GetTeamMembers()
|
||||
if err != nil {
|
||||
// Non-fatal - we can still work without team members
|
||||
teamMembers = []models.TeamMember{}
|
||||
}
|
||||
return dataLoadedMsg{iterations: iterations, areas: areas, statesByType: statesByType, teamMembers: teamMembers}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -530,3 +581,13 @@ func createBranchCmd(branchName string) tea.Cmd {
|
||||
return components.BranchCreatedMsg{BranchName: branchName}
|
||||
}
|
||||
}
|
||||
|
||||
func assignWorkItemCmd(client *api.Client, itemID int, userEmail, userName string, filterState *models.FilterState) tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
err := client.AssignWorkItem(itemID, userEmail)
|
||||
if err != nil {
|
||||
return errMsg{err: err}
|
||||
}
|
||||
return assignedMsg{userName: userName}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user