diff --git a/app/javascript/dashboard/routes/dashboard/contacts/components/ContactsBulkActionBar.vue b/app/javascript/dashboard/routes/dashboard/contacts/components/ContactsBulkActionBar.vue
index e56a2dda7..d19f84d99 100644
--- a/app/javascript/dashboard/routes/dashboard/contacts/components/ContactsBulkActionBar.vue
+++ b/app/javascript/dashboard/routes/dashboard/contacts/components/ContactsBulkActionBar.vue
@@ -66,8 +66,7 @@ const selectionModel = computed({
return;
}
- const shouldSelectAll =
- newSet.size === props.visibleContactIds.length && newSet.size > 0;
+ const shouldSelectAll = props.visibleContactIds.every(id => newSet.has(id));
emit('toggleAll', shouldSelectAll);
},
});
diff --git a/app/javascript/dashboard/routes/dashboard/contacts/pages/ContactsIndex.vue b/app/javascript/dashboard/routes/dashboard/contacts/pages/ContactsIndex.vue
index 357758e01..b072fa67d 100644
--- a/app/javascript/dashboard/routes/dashboard/contacts/pages/ContactsIndex.vue
+++ b/app/javascript/dashboard/routes/dashboard/contacts/pages/ContactsIndex.vue
@@ -151,7 +151,13 @@ const openBulkDeleteDialog = () => {
};
const toggleSelectAll = shouldSelect => {
- selectedContactIds.value = shouldSelect ? [...visibleContactIds.value] : [];
+ const currentSelection = new Set(selectedContactIds.value);
+ if (shouldSelect) {
+ visibleContactIds.value.forEach(id => currentSelection.add(id));
+ } else {
+ visibleContactIds.value.forEach(id => currentSelection.delete(id));
+ }
+ selectedContactIds.value = Array.from(currentSelection);
};
const toggleContactSelection = ({ id, value }) => {
@@ -190,16 +196,28 @@ const getCommonFetchParams = (page = 1) => ({
label: activeLabel.value,
});
-const fetchContacts = async (page = 1) => {
- clearSelection();
+const fetchContacts = async (page = 1, options = {}) => {
+ const { clearSelection: shouldClearSelection = true } = options;
+ if (shouldClearSelection) {
+ clearSelection();
+ }
await store.dispatch('contacts/clearContactFilters');
await store.dispatch('contacts/get', getCommonFetchParams(page));
updatePageParam(page);
};
-const fetchSavedOrAppliedFilteredContact = async (payload, page = 1) => {
+const fetchSavedOrAppliedFilteredContact = async (
+ payload,
+ page = 1,
+ options = {}
+) => {
if (!activeSegmentId.value && !hasAppliedFilters.value) return;
- clearSelection();
+
+ const { clearSelection: shouldClearSelection = true } = options;
+ if (shouldClearSelection) {
+ clearSelection();
+ }
+
await store.dispatch('contacts/filter', {
...getCommonFetchParams(page),
queryPayload: payload,
@@ -207,8 +225,12 @@ const fetchSavedOrAppliedFilteredContact = async (payload, page = 1) => {
updatePageParam(page);
};
-const fetchActiveContacts = async (page = 1) => {
- clearSelection();
+const fetchActiveContacts = async (page = 1, options = {}) => {
+ const { clearSelection: shouldClearSelection = true } = options;
+ if (shouldClearSelection) {
+ clearSelection();
+ }
+
await store.dispatch('contacts/clearContactFilters');
await store.dispatch('contacts/active', {
page,
@@ -217,28 +239,36 @@ const fetchActiveContacts = async (page = 1) => {
updatePageParam(page);
};
-const searchContacts = debounce(async (value, page = 1, append = false) => {
- if (!append) {
- clearSelection();
- searchPageNumber.value = 1;
- }
- await store.dispatch('contacts/clearContactFilters');
- searchValue.value = value;
+const searchContacts = debounce(
+ async (value, page = 1, append = false, options = {}) => {
+ const { clearSelection: shouldClearSelection = true } = options;
- if (!value) {
- updatePageParam(page);
- await fetchContacts(page);
- return;
- }
+ if (!append) {
+ searchPageNumber.value = 1;
- updatePageParam(page, value);
- await store.dispatch('contacts/search', {
- ...getCommonFetchParams(page),
- search: encodeURIComponent(value),
- append,
- });
- searchPageNumber.value = page;
-}, DEBOUNCE_DELAY);
+ if (shouldClearSelection) {
+ clearSelection();
+ }
+ }
+ await store.dispatch('contacts/clearContactFilters');
+ searchValue.value = value;
+
+ if (!value) {
+ updatePageParam(page);
+ await fetchContacts(page, { clearSelection: false });
+ return;
+ }
+
+ updatePageParam(page, value);
+ await store.dispatch('contacts/search', {
+ ...getCommonFetchParams(page),
+ search: encodeURIComponent(value),
+ append,
+ });
+ searchPageNumber.value = page;
+ },
+ DEBOUNCE_DELAY
+);
const loadMoreSearchResults = async () => {
if (!hasMore.value || isLoadingMore.value) return;
@@ -256,19 +286,26 @@ const loadMoreSearchResults = async () => {
isLoadingMore.value = false;
};
-const fetchContactsBasedOnContext = async page => {
- clearSelection();
+const fetchContactsBasedOnContext = async (page, options = {}) => {
+ const { clearSelection: shouldClearSelection = true } = options;
+ if (shouldClearSelection) {
+ clearSelection();
+ }
updatePageParam(page, searchValue.value);
if (isFetchingList.value) return;
if (searchQuery.value) {
- await searchContacts(searchQuery.value, page);
+ await searchContacts(searchQuery.value, page, false, {
+ clearSelection: shouldClearSelection,
+ });
return;
}
// Reset the search value when we change the view
searchValue.value = '';
// If we're on the active route, fetch active contacts
if (isActiveView.value) {
- await fetchActiveContacts(page);
+ await fetchActiveContacts(page, {
+ clearSelection: shouldClearSelection,
+ });
return;
}
// If there are applied filters or active segment with query
@@ -278,13 +315,20 @@ const fetchContactsBasedOnContext = async page => {
) {
const queryPayload =
activeSegment.value?.query || filterQueryGenerator(appliedFilters.value);
- await fetchSavedOrAppliedFilteredContact(queryPayload, page);
+ await fetchSavedOrAppliedFilteredContact(queryPayload, page, {
+ clearSelection: shouldClearSelection,
+ });
return;
}
// Default case: fetch regular contacts + label
- await fetchContacts(page);
+ await fetchContacts(page, {
+ clearSelection: shouldClearSelection,
+ });
};
+const onPageChange = page =>
+ fetchContactsBasedOnContext(page, { clearSelection: false });
+
const assignLabels = async labels => {
if (!labels.length || !selectedContactIds.value.length) {
return;
@@ -338,7 +382,9 @@ const handleSort = async ({ sort, order }) => {
});
if (searchQuery.value) {
- await searchContacts(searchValue.value);
+ await searchContacts(searchValue.value, pageNumber.value, false, {
+ clearSelection: false,
+ });
return;
}
@@ -360,17 +406,6 @@ const createContact = async contact => {
await store.dispatch('contacts/create', contact);
};
-watch(
- contacts,
- newContacts => {
- const idsOnPage = newContacts.map(contact => contact.id);
- selectedContactIds.value = selectedContactIds.value.filter(id =>
- idsOnPage.includes(id)
- );
- },
- { deep: true }
-);
-
watch(hasSelection, value => {
if (!value) {
bulkDeleteDialogRef.value?.close?.();
@@ -416,7 +451,9 @@ watch(searchQuery, value => {
onMounted(async () => {
if (!activeSegmentId.value) {
if (searchQuery.value) {
- await searchContacts(searchQuery.value, pageNumber.value);
+ await searchContacts(searchQuery.value, pageNumber.value, false, {
+ clearSelection: false,
+ });
return;
}
if (isActiveView.value) {
@@ -452,8 +489,10 @@ onMounted(async () => {
:use-infinite-scroll="isSearchView"
:has-more="hasMore"
:is-loading-more="isLoadingMore"
- @update:current-page="fetchContactsBasedOnContext"
- @search="searchContacts"
+ @update:current-page="onPageChange"
+ @search="
+ value => searchContacts(value, 1, false, { clearSelection: false })
+ "
@update:sort="handleSort"
@apply-filter="fetchSavedOrAppliedFilteredContact"
@clear-filters="fetchContacts"
@@ -485,6 +524,7 @@ onMounted(async () => {
:button-label="t('CONTACTS_LAYOUT.EMPTY_STATE.BUTTON_LABEL')"
@create="createContact"
/>
+
{
{{ emptyStateMessage }}
+