chore: Improvements in pending FAQs (#12755)
# Pull Request Template ## Description **This PR includes:** 1. Added URL-based filter persistence for the responses pages, including page and search parameters. 2. Introduced a new empty state variant for pending FAQs — without a backdrop and with a “Clear Filters” option. 3. Made the actions, filter, and search row remain fixed at the top while scrolling. Fixes https://linear.app/chatwoot/issue/CW-5852/improvements-in-pending-faqs ## Type of change - [x] New feature (non-breaking change which adds functionality) ## How Has This Been Tested? ### Loom video https://www.loom.com/share/1d9eee68c0684f0ab05e08b4ca1e0ce9 ## Checklist: - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my code - [ ] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published in downstream modules
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
import { computed, onMounted, ref, nextTick } from 'vue';
|
||||
import { useMapGetter, useStore } from 'dashboard/composables/store';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useRouter, useRoute } from 'vue-router';
|
||||
import { FEATURE_FLAGS } from 'dashboard/featureFlags';
|
||||
import { debounce } from '@chatwoot/utils';
|
||||
import { useAccount } from 'dashboard/composables/useAccount';
|
||||
@@ -23,6 +23,7 @@ import FeatureSpotlightPopover from 'dashboard/components-next/feature-spotlight
|
||||
import LimitBanner from 'dashboard/components-next/captain/pageComponents/response/LimitBanner.vue';
|
||||
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const store = useStore();
|
||||
const { isOnChatwootCloud } = useAccount();
|
||||
const uiFlags = useMapGetter('captainResponses/getUIFlags');
|
||||
@@ -90,6 +91,18 @@ const handleCreateClose = () => {
|
||||
selectedResponse.value = null;
|
||||
};
|
||||
|
||||
const updateURLWithFilters = (page, search) => {
|
||||
const query = {
|
||||
page: page || 1,
|
||||
};
|
||||
|
||||
if (search) {
|
||||
query.search = search;
|
||||
}
|
||||
|
||||
router.replace({ query });
|
||||
};
|
||||
|
||||
const fetchResponses = (page = 1) => {
|
||||
const filterParams = { page, status: 'approved' };
|
||||
|
||||
@@ -99,6 +112,10 @@ const fetchResponses = (page = 1) => {
|
||||
if (searchQuery.value) {
|
||||
filterParams.search = searchQuery.value;
|
||||
}
|
||||
|
||||
// Update URL with current filters
|
||||
updateURLWithFilters(page, searchQuery.value);
|
||||
|
||||
store.dispatch('captainResponses/get', filterParams);
|
||||
};
|
||||
|
||||
@@ -186,20 +203,28 @@ const onBulkDeleteSuccess = () => {
|
||||
|
||||
const handleAssistantFilterChange = assistant => {
|
||||
selectedAssistant.value = assistant;
|
||||
fetchResponses();
|
||||
fetchResponses(1);
|
||||
};
|
||||
|
||||
const debouncedSearch = debounce(async () => {
|
||||
fetchResponses();
|
||||
fetchResponses(1);
|
||||
}, 500);
|
||||
|
||||
const initializeFromURL = () => {
|
||||
if (route.query.search) {
|
||||
searchQuery.value = route.query.search;
|
||||
}
|
||||
const pageFromURL = parseInt(route.query.page, 10) || 1;
|
||||
fetchResponses(pageFromURL);
|
||||
};
|
||||
|
||||
const navigateToPendingFAQs = () => {
|
||||
router.push({ name: 'captain_responses_pending' });
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
store.dispatch('captainAssistants/get');
|
||||
fetchResponses();
|
||||
initializeFromURL();
|
||||
store.dispatch('captainResponses/fetchPendingCount');
|
||||
});
|
||||
</script>
|
||||
@@ -230,18 +255,10 @@ onMounted(() => {
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template #emptyState>
|
||||
<ResponsePageEmptyState @click="handleCreate" />
|
||||
</template>
|
||||
|
||||
<template #paywall>
|
||||
<CaptainPaywall />
|
||||
</template>
|
||||
|
||||
<template #controls>
|
||||
<template #subHeader>
|
||||
<div
|
||||
v-if="shouldShowDropdown"
|
||||
class="mb-4 -mt-3 flex justify-between items-center py-1"
|
||||
class="mb-2 flex justify-between items-center py-1"
|
||||
:class="{
|
||||
'ltr:pl-3 rtl:pr-3 ltr:pr-1 rtl:pl-1 rounded-lg outline outline-1 outline-n-weak bg-n-solid-3 w-fit':
|
||||
bulkSelectionState.hasSelected,
|
||||
@@ -314,12 +331,20 @@ onMounted(() => {
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #emptyState>
|
||||
<ResponsePageEmptyState @click="handleCreate" />
|
||||
</template>
|
||||
|
||||
<template #paywall>
|
||||
<CaptainPaywall />
|
||||
</template>
|
||||
|
||||
<template #body>
|
||||
<LimitBanner class="mb-5" />
|
||||
<Banner
|
||||
v-if="pendingCount > 0"
|
||||
color="blue"
|
||||
class="mb-4"
|
||||
class="mb-4 -mt-3"
|
||||
:action-label="$t('CAPTAIN.RESPONSES.PENDING_BANNER.ACTION')"
|
||||
@action="navigateToPendingFAQs"
|
||||
>
|
||||
|
||||
@@ -119,6 +119,18 @@ const handleCreateClose = () => {
|
||||
selectedResponse.value = null;
|
||||
};
|
||||
|
||||
const updateURLWithFilters = (page, search) => {
|
||||
const query = {
|
||||
page: page || 1,
|
||||
};
|
||||
|
||||
if (search) {
|
||||
query.search = search;
|
||||
}
|
||||
|
||||
router.replace({ query });
|
||||
};
|
||||
|
||||
const fetchResponses = (page = 1) => {
|
||||
const filterParams = { page, status: 'pending' };
|
||||
|
||||
@@ -128,6 +140,10 @@ const fetchResponses = (page = 1) => {
|
||||
if (searchQuery.value) {
|
||||
filterParams.search = searchQuery.value;
|
||||
}
|
||||
|
||||
// Update URL with current filters
|
||||
updateURLWithFilters(page, searchQuery.value);
|
||||
|
||||
store.dispatch('captainResponses/get', filterParams);
|
||||
};
|
||||
|
||||
@@ -225,16 +241,34 @@ const onBulkDeleteSuccess = () => {
|
||||
|
||||
const handleAssistantFilterChange = assistant => {
|
||||
selectedAssistant.value = assistant;
|
||||
fetchResponses();
|
||||
fetchResponses(1);
|
||||
};
|
||||
|
||||
const debouncedSearch = debounce(async () => {
|
||||
fetchResponses();
|
||||
fetchResponses(1);
|
||||
}, 500);
|
||||
|
||||
const hasActiveFilters = computed(() => {
|
||||
return Boolean(searchQuery.value || selectedAssistant.value !== 'all');
|
||||
});
|
||||
|
||||
const clearFilters = () => {
|
||||
searchQuery.value = '';
|
||||
selectedAssistant.value = 'all';
|
||||
fetchResponses(1);
|
||||
};
|
||||
|
||||
const initializeFromURL = () => {
|
||||
if (route.query.search) {
|
||||
searchQuery.value = route.query.search;
|
||||
}
|
||||
const pageFromURL = parseInt(route.query.page, 10) || 1;
|
||||
fetchResponses(pageFromURL);
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
store.dispatch('captainAssistants/get');
|
||||
fetchResponses();
|
||||
initializeFromURL();
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -265,18 +299,10 @@ onMounted(() => {
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template #emptyState>
|
||||
<ResponsePageEmptyState @click="handleCreate" />
|
||||
</template>
|
||||
|
||||
<template #paywall>
|
||||
<CaptainPaywall />
|
||||
</template>
|
||||
|
||||
<template #controls>
|
||||
<template #subHeader>
|
||||
<div
|
||||
v-if="shouldShowDropdown"
|
||||
class="mb-4 -mt-3 flex justify-between items-center py-1"
|
||||
class="mb-2 flex justify-between items-center py-1"
|
||||
:class="{
|
||||
'ltr:pl-3 rtl:pr-3 ltr:pr-1 rtl:pl-1 rounded-lg outline outline-1 outline-n-weak bg-n-solid-3 w-fit':
|
||||
bulkSelectionState.hasSelected,
|
||||
@@ -358,6 +384,19 @@ onMounted(() => {
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #emptyState>
|
||||
<ResponsePageEmptyState
|
||||
variant="pending"
|
||||
:has-active-filters="hasActiveFilters"
|
||||
@click="handleCreate"
|
||||
@clear-filters="clearFilters"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template #paywall>
|
||||
<CaptainPaywall />
|
||||
</template>
|
||||
|
||||
<template #body>
|
||||
<LimitBanner class="mb-5" />
|
||||
|
||||
|
||||
Reference in New Issue
Block a user