Replace assignee-based tabs (Mine/Unassigned/All) with 5 sidebar filters and 2 status tabs:
- All Waiting:
team_id IS NULL AND assignee_id IS NULL - Team Waiting:
team_id IN (user's teams) AND assignee_id IS NULL - My Chats:
assignee_id = current_user.id - My Team Chats:
assignee_id IN (team_member_ids) AND assignee_id != current_user.id - All Chats: All other conversations (assigned to other teams/agents)
- In Progress:
status IN ('open', 'pending', 'snoozed') - Done:
status = 'resolved'
- Button appears on conversation cards in "All Waiting" and "Team Waiting" filters
- Also available in right-click context menu
- Assigns conversation to current user via existing
POST /assignmentsendpoint - Only visible when
assignee_id IS NULL
- ✅ Pending/snoozed conversations appear in "In Progress" tab
- ✅ "All Chats" filter respects current status tab selection
- ✅ Accept button appears BOTH on card AND in context menu
- ✅ Team filtering uses ALL teams the current user belongs to
Replace tabs with sidebar filters → Update filter logic → Add Accept button → Modify store getters
Extend ConversationFinder → Add new filter types → Return new counts → Handle team-based queries
-
app/javascript/dashboard/components/ChatList.vue(Lines 271-343)- Change
activeAssigneeTab→activeFilterType - Update
conversationFilterscomputed:assigneeType→filterType - Modify
conversationListcomputed to handle 5 filter types - Add
acceptConversation()function and provide it to children - Replace
ChatTypeTabscomponent with newConversationSidebarFilters
- Change
-
app/javascript/dashboard/constants/globals.js- Add new constant:
SIDEBAR_FILTER_TYPE: { ALL_WAITING: 'all_waiting', TEAM_WAITING: 'team_waiting', MY_CHATS: 'my_chats', MY_TEAM_CHATS: 'my_team_chats', ALL_CHATS: 'all_chats' }
-
app/javascript/dashboard/store/modules/conversations/getters.js- Add 3 new getters:
getAllWaitingChats: Filter conversations with no team and no assigneegetTeamWaitingChats: Filter by user's team IDs where unassignedgetMyTeamChatsExcludingMine: Filter by user's teams where assigned to teammate
- Add 3 new getters:
-
app/javascript/dashboard/components/widgets/conversation/ConversationCard.vue- Add
showAcceptButtonprop - Add Accept button in card UI (Tailwind styled)
- Emit
acceptConversationevent
- Add
-
app/javascript/dashboard/components/widgets/conversation/contextMenu/Index.vue- Add Accept menu item
- Show only when conversation is unassigned
- Emit
acceptConversationevent
app/finders/conversation_finder.rb(Lines 109-175)- Extend
filter_by_assignee_type()to handle new filter types:
when 'all_waiting' @conversations = @conversations.unassigned.where(team_id: nil) when 'team_waiting' user_team_ids = current_user.teams.pluck(:id) @conversations = @conversations.unassigned.where(team_id: user_team_ids) when 'my_team_chats' user_team_ids = current_user.teams.pluck(:id) @conversations = @conversations .where(team_id: user_team_ids) .where.not(assignee_id: current_user.id) .where.not(assignee_id: nil)
- Update
filter_by_status()to accept array of statuses for "In Progress" tab - Modify
set_count_for_all_conversations()to return 6 counts instead of 3 - Update
perform()return value to include new counts
- Extend
-
app/javascript/dashboard/components/widgets/ConversationSidebarFilters.vue(NEW)- Renders vertical list of 5 filters with icons and counts
- Emits
filterChange(filterKey)on selection - Shows active filter state
-
app/javascript/dashboard/components/widgets/ConversationStatusTabs.vue(NEW)- Renders 2 tabs: "In Progress" and "Done"
- Similar to ChatTypeTabs but for status
- Emits
statusChange(statusKey)
-
Update conversation_finder.rb
- Add new filter types to
filter_by_assignee_type() - Implement team member filtering logic
- Modify
filter_by_status()to accept status arrays - Update
set_count_for_all_conversations()to return 6 counts - Update
perform()method return hash
- Add new filter types to
-
Test backend changes
- Run:
bundle exec rspec spec/finders/conversation_finder_spec.rb - Add new specs for 'all_waiting', 'team_waiting', 'my_team_chats' filter types
- Run:
-
Add filter constants
- Update
globals.jswithSIDEBAR_FILTER_TYPE
- Update
-
Add store getters
- Add
getAllWaitingChats,getTeamWaitingChats,getMyTeamChatsExcludingMineto getters.js - Each getter filters conversations client-side based on team membership and assignment
- Add
-
Create ConversationSidebarFilters.vue
- Vertical list layout with Tailwind classes
- Show filter name, icon, and count
- Highlight active filter
- Emit filterChange event
-
Create ConversationStatusTabs.vue
- Two-tab layout (In Progress / Done)
- Similar structure to existing ChatTypeTabs
- Emit statusChange event
- Modify ChatList.vue
- Remove ChatTypeTabs import/usage
- Import new ConversationSidebarFilters and ConversationStatusTabs
- Rename
activeAssigneeTab→activeFilterType - Update
conversationFilterscomputed property - Update
conversationListcomputed with switch statement for 5 filters - Add
acceptConversation()function - Provide
acceptConversationto child components
-
Update ConversationCard.vue
- Add
showAcceptButtonprop - Add Accept button in card footer area
- Emit
acceptConversationevent with conversation ID - Style with Tailwind utilities
- Add
-
Update contextMenu/Index.vue
- Add Accept option to menu constants
- Add Accept menu item in template
- Show only when
!chat.meta.assignee - Emit
acceptConversationevent
-
Update ConversationItem.vue
- Inject
acceptConversationfunction - Pass
showAcceptButtonprop to ConversationCard - Calculate visibility based on active filter
- Forward acceptConversation events
- Inject
- Update i18n files
- Add translations to
app/javascript/dashboard/i18n/locale/en/chatlist.json - Add SIDEBAR_FILTERS translations
- Add STATUS_TABS translations
- Add Accept button labels
- Add translations to
-
Write frontend tests
- Test new components (ConversationSidebarFilters, ConversationStatusTabs)
- Update ChatList.spec.js for new filter logic
- Test Accept button visibility logic
- Test acceptConversation action
-
Write backend tests
- Update conversation_finder_spec.rb
- Test new filter types
- Test team-based filtering
- Test count calculations
User clicks filter → ConversationSidebarFilters emits filterChange
→ ChatList updates activeFilterType
→ conversationFilters computed recalculates
→ Store getters filter conversations
→ UI updates
User clicks Accept → Event bubbles to ChatList
→ acceptConversation(conversationId) called
→ Vuex action 'assignAgent' dispatched
→ POST /api/v1/accounts/{id}/conversations/{id}/assignments
→ Backend assigns conversation to user
→ Real-time update removes from current filter
→ Conversation appears in "My Chats"
-
User not in any teams
- "Team Waiting" and "My Team Chats" show empty state
- Backend returns empty array for user_team_ids
-
Conversation with team_id AND assignee_id
- Appears in "My Chats" if assigned to current user (priority)
- Appears in "My Team Chats" if assigned to teammate
- Does NOT appear in "Team Waiting" (not unassigned)
-
Multiple team membership
- User sees conversations from ALL their teams
- Query uses
team_id IN (all_user_team_ids)
-
Accept button visibility
- Only show in "All Waiting" and "Team Waiting" filters
- Only show when
assignee_id IS NULL - Hide in other filters automatically
-
Test sidebar filters
- Log in as user with multiple team memberships - Click "All Waiting" → Should show conversations with no team, no assignee - Click "Team Waiting" → Should show team conversations with no assignee - Click "My Chats" → Should show conversations assigned to you - Click "My Team Chats" → Should show teammate conversations - Click "All Chats" → Should show remaining conversations -
Test status tabs
- Click "In Progress" → Should show open, pending, snoozed conversations - Click "Done" → Should show resolved conversations - Switch between tabs in each filter -
Test Accept button
- Navigate to "All Waiting" filter - Verify Accept button appears on unassigned conversations - Click Accept → Conversation should disappear from list - Navigate to "My Chats" → Should see accepted conversation - Test Accept from context menu (right-click) - Navigate to "Team Waiting" and repeat Accept test -
Test edge cases
- Log in as user with no teams → Team filters should be empty - Test with conversations assigned to other teams - Test Accept race condition (two users clicking simultaneously)
-
Run test suites
# Backend tests bundle exec rspec spec/finders/conversation_finder_spec.rb bundle exec rspec spec/controllers/api/v1/accounts/conversations_controller_spec.rb # Frontend tests pnpm test -- ConversationSidebarFilters.spec.js pnpm test -- ChatList.spec.js pnpm test -- ConversationCard.spec.js
-
Run linters
pnpm eslint bundle exec rubocop -a -
Integration test
# Start dev environment pnpm dev # Open browser and verify: # - All filters work correctly # - Counts update in real-time # - Accept button assigns conversations # - Status tabs filter correctly
- Keep old
assigneeTypeparameter support during transition - Map old values:
'me'→'my_chats','unassigned'→'all_waiting','all'→'all_chats' - Custom views use advanced filters (unaffected by this change)
- No database migrations required
If issues arise:
- No database changes → Rollback is safe
- Revert frontend to show old ChatTypeTabs component
- Backend remains backward compatible with old filter types
- Consider adding feature flag for gradual rollout
Before implementation:
- Check
enterprise/app/finders/enterprise/conversation_finder.rbfor overrides - Test compatibility with Enterprise team hierarchies
- Update Enterprise specs to mirror OSS changes
- Verify SLA policies work with new filters
- New implementation adds 3 additional count queries
- Client-side filtering uses cached Vuex getters (no performance impact expected)
- Virtual scrolling already handles large conversation lists
- Consider adding database index:
conversations(team_id, assignee_id, status)if queries slow down
Add to app/javascript/dashboard/i18n/locale/en/chatlist.json:
{
"CHAT_LIST": {
"SIDEBAR_FILTERS": {
"all_waiting": "All Waiting",
"team_waiting": "Team Waiting",
"my_chats": "My Chats",
"my_team_chats": "My Team Chats",
"all_chats": "All Chats"
},
"STATUS_TABS": {
"in_progress": "In Progress",
"done": "Done"
}
},
"CONVERSATION": {
"ACCEPT": "Accept",
"ACCEPT_SUCCESS": "Conversation assigned to you",
"ACCEPT_FAILED": "Failed to accept conversation",
"CARD_CONTEXT_MENU": {
"ACCEPT": "Accept conversation"
}
}
}This refactoring modernizes the conversation sidebar by:
- Replacing 3 assignee tabs with 5 granular filters
- Adding dedicated status tabs for better workflow organization
- Implementing one-click Accept functionality for faster conversation assignment
- Improving team-based conversation management
The implementation maintains backward compatibility, requires no database migrations, and follows Chatwoot's existing patterns for minimal risk and maximum maintainability.