diff --git a/frontend/apps/main/src/features/conversation/list/ConversationList.vue b/frontend/apps/main/src/features/conversation/list/ConversationList.vue
index 40821ba5..6b665cae 100644
--- a/frontend/apps/main/src/features/conversation/list/ConversationList.vue
+++ b/frontend/apps/main/src/features/conversation/list/ConversationList.vue
@@ -6,8 +6,113 @@
{{ title }}
-
-
+
+
+
+
+ {{ t('conversation.bulkActions.selected', conversationStore.selectedCount, { count: conversationStore.selectedCount }) }}
+
+
+
+
+
+
+
+
+
+
+ {{ t('globals.terms.agent', 2) }}
+
+
+ {{ agent.label }}
+
+
+
+
+
+ {{ t('globals.terms.team', 2) }}
+
+
+ {{ team.label }}
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ status.label }}
+
+
+
+
+
+
+
+
+
+
+
+ {{ priority.label }}
+
+
+
+
+
+
+
+
+
+
+
@@ -149,26 +254,61 @@
diff --git a/frontend/apps/main/src/stores/conversation.js b/frontend/apps/main/src/stores/conversation.js
index a1c2f96b..860c68ca 100644
--- a/frontend/apps/main/src/stores/conversation.js
+++ b/frontend/apps/main/src/stores/conversation.js
@@ -25,6 +25,9 @@ export const useConversationStore = defineStore('conversation', () => {
const macros = ref({})
const drafts = ref(new Map())
+ // Bulk selection state
+ const selectedUUIDs = ref(new Set())
+
// Options for select fields
const priorityOptions = computed(() => {
return priorities.value.map(p => ({ label: p.name, value: p.id }))
@@ -40,6 +43,51 @@ export const useConversationStore = defineStore('conversation', () => {
}))
)
+ // Bulk selection methods
+ let lastClickedUUID = null
+
+ const selectedCount = computed(() => selectedUUIDs.value.size)
+ const allSelected = computed(() => {
+ const list = conversationsList.value
+ return list.length > 0 && selectedUUIDs.value.size === list.length
+ })
+
+ function toggleSelect (uuid, shiftKey = false) {
+ const next = new Set(selectedUUIDs.value)
+
+ if (shiftKey && lastClickedUUID && lastClickedUUID !== uuid) {
+ const list = conversationsList.value
+ const lastIdx = list.findIndex(c => c.uuid === lastClickedUUID)
+ const curIdx = list.findIndex(c => c.uuid === uuid)
+ if (lastIdx !== -1 && curIdx !== -1) {
+ const start = Math.min(lastIdx, curIdx)
+ const end = Math.max(lastIdx, curIdx)
+ for (let i = start; i <= end; i++) {
+ next.add(list[i].uuid)
+ }
+ }
+ } else {
+ if (next.has(uuid)) next.delete(uuid)
+ else next.add(uuid)
+ }
+
+ lastClickedUUID = uuid
+ selectedUUIDs.value = next
+ }
+
+ function selectAll () {
+ selectedUUIDs.value = new Set(conversationsList.value.map(c => c.uuid))
+ }
+
+ function clearSelection () {
+ selectedUUIDs.value = new Set()
+ lastClickedUUID = null
+ }
+
+ function isSelected (uuid) {
+ return selectedUUIDs.value.has(uuid)
+ }
+
// TODO: Move to constants.
const sortFieldMap = {
oldest: {
@@ -798,6 +846,7 @@ export const useConversationStore = defineStore('conversation', () => {
conversations.data = []
conversations.page = 1
seenConversationUUIDs = new Map()
+ clearSelection()
}
/** Macros set for new conversation or an open conversation **/
@@ -975,6 +1024,13 @@ export const useConversationStore = defineStore('conversation', () => {
hasDraft,
addPendingMessage,
replacePendingMessage,
- removePendingMessage
+ removePendingMessage,
+ selectedUUIDs,
+ selectedCount,
+ allSelected,
+ toggleSelect,
+ selectAll,
+ clearSelection,
+ isSelected
}
})
diff --git a/i18n/en.json b/i18n/en.json
index 58ae7c04..d6cb5a05 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -467,6 +467,14 @@
"contextLink.urlTemplateHelp": "{'{{token}}'} is a base64-encoded AES-256-GCM encrypted blob containing all contact and agent fields (requires secret). Individual variables like {'{{email}}'}, {'{{phone}}'}, {'{{external_user_id}}'}, {'{{contact_id}}'}, {'{{first_name}}'}, {'{{last_name}}'}, {'{{conversation_uuid}}'} are passed as plain text.",
"conversation.agentAssigned": "Agent assigned",
"conversation.allLoaded": "All conversations loaded",
+ "conversation.bulkActions.assign": "Assign",
+ "conversation.bulkActions.clearSelection": "Clear selection",
+ "conversation.bulkActions.failedToast": "Updated {success}, failed {failed} of {total} conversations",
+ "conversation.bulkActions.selectAll": "Select all conversations",
+ "conversation.bulkActions.selectConversation": "Select conversation",
+ "conversation.bulkActions.selected": "No conversations selected | {count} selected | {count} selected",
+ "conversation.bulkActions.successToast": "Updated {count} conversation | Updated {count} conversations",
+ "conversation.bulkActions.toolbar": "Bulk actions toolbar",
"conversation.couldNotFetch": "Could not fetch conversations",
"conversation.hideQuotedText": "Hide quoted text",
"conversation.mentions": "Mentions",