From 8f4b252045ac8dbb8e2289b8517c6f47d0b86d8c Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Thu, 18 Sep 2025 14:44:56 +0530 Subject: [PATCH] feat: allow searching captain responses [CW-5631] (#12463) --- .../dashboard/api/captain/response.js | 4 +- .../dashboard/components-next/input/Input.vue | 19 +++++- .../i18n/locale/en/integrations.json | 1 + .../dashboard/captain/responses/Index.vue | 67 +++++++++++++------ .../captain/assistant_responses_controller.rb | 43 +++++++----- .../assistant_responses_controller_spec.rb | 47 +++++++++++++ 6 files changed, 140 insertions(+), 41 deletions(-) diff --git a/app/javascript/dashboard/api/captain/response.js b/app/javascript/dashboard/api/captain/response.js index e3c42757a..d48bd81c7 100644 --- a/app/javascript/dashboard/api/captain/response.js +++ b/app/javascript/dashboard/api/captain/response.js @@ -6,11 +6,11 @@ class CaptainResponses extends ApiClient { super('captain/assistant_responses', { accountScoped: true }); } - get({ page = 1, searchKey, assistantId, documentId, status } = {}) { + get({ page = 1, search, assistantId, documentId, status } = {}) { return axios.get(this.url, { params: { page, - searchKey, + search, assistant_id: assistantId, document_id: documentId, status, diff --git a/app/javascript/dashboard/components-next/input/Input.vue b/app/javascript/dashboard/components-next/input/Input.vue index f4bf5a94f..71964b4f8 100644 --- a/app/javascript/dashboard/components-next/input/Input.vue +++ b/app/javascript/dashboard/components-next/input/Input.vue @@ -7,6 +7,11 @@ const props = defineProps({ placeholder: { type: String, default: '' }, label: { type: String, default: '' }, id: { type: String, default: '' }, + size: { + type: String, + default: 'md', + validator: value => ['sm', 'md'].includes(value), + }, message: { type: String, default: '' }, disabled: { type: Boolean, default: false }, messageType: { @@ -69,6 +74,17 @@ const handleFocus = event => { isFocused.value = true; }; +const sizeClass = computed(() => { + switch (props.size) { + case 'sm': + return 'h-8 !px-3 !py-2'; + case 'md': + return 'h-10 !px-3 !py-2.5'; + default: + return 'h-10 !px-3 !py-2.5'; + } +}); + const handleBlur = event => { emit('blur', event); isFocused.value = false; @@ -105,6 +121,7 @@ onMounted(() => { :class="[ customInputClass, inputOutlineClass, + sizeClass, { error: messageType === 'error', focus: isFocused, @@ -119,7 +136,7 @@ onMounted(() => { ? max : undefined " - class="block w-full reset-base text-sm h-10 !px-3 !py-2.5 !mb-0 outline outline-1 border-none border-0 outline-offset-[-1px] rounded-lg bg-n-alpha-black2 file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-n-slate-10 dark:placeholder:text-n-slate-10 disabled:cursor-not-allowed disabled:opacity-50 text-n-slate-12 transition-all duration-500 ease-in-out [appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none" + class="block w-full reset-base text-sm !mb-0 outline outline-1 border-none border-0 outline-offset-[-1px] rounded-lg bg-n-alpha-black2 file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-n-slate-10 dark:placeholder:text-n-slate-10 disabled:cursor-not-allowed disabled:opacity-50 text-n-slate-12 transition-all duration-500 ease-in-out [appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none" @input="handleInput" @focus="handleFocus" @blur="handleBlur" diff --git a/app/javascript/dashboard/i18n/locale/en/integrations.json b/app/javascript/dashboard/i18n/locale/en/integrations.json index c4399b0e9..8a812dff3 100644 --- a/app/javascript/dashboard/i18n/locale/en/integrations.json +++ b/app/javascript/dashboard/i18n/locale/en/integrations.json @@ -759,6 +759,7 @@ "SELECTED": "{count} selected", "SELECT_ALL": "Select all ({count})", "UNSELECT_ALL": "Unselect all ({count})", + "SEARCH_PLACEHOLDER": "Search FAQs...", "BULK_APPROVE_BUTTON": "Approve", "BULK_DELETE_BUTTON": "Delete", "BULK_APPROVE": { diff --git a/app/javascript/dashboard/routes/dashboard/captain/responses/Index.vue b/app/javascript/dashboard/routes/dashboard/captain/responses/Index.vue index 85e7f1ed0..86d3fff69 100644 --- a/app/javascript/dashboard/routes/dashboard/captain/responses/Index.vue +++ b/app/javascript/dashboard/routes/dashboard/captain/responses/Index.vue @@ -6,10 +6,12 @@ import { useI18n } from 'vue-i18n'; import { OnClickOutside } from '@vueuse/components'; import { useRouter } from 'vue-router'; import { FEATURE_FLAGS } from 'dashboard/featureFlags'; +import { debounce } from '@chatwoot/utils'; import Button from 'dashboard/components-next/button/Button.vue'; import Checkbox from 'dashboard/components-next/checkbox/Checkbox.vue'; import DropdownMenu from 'dashboard/components-next/dropdown-menu/DropdownMenu.vue'; +import Input from 'dashboard/components-next/input/Input.vue'; import DeleteDialog from 'dashboard/components-next/captain/pageComponents/DeleteDialog.vue'; import BulkDeleteDialog from 'dashboard/components-next/captain/pageComponents/BulkDeleteDialog.vue'; import PageLayout from 'dashboard/components-next/captain/PageLayout.vue'; @@ -36,6 +38,7 @@ const bulkDeleteDialog = ref(null); const selectedStatus = ref('all'); const selectedAssistant = ref('all'); const dialogType = ref(''); +const searchQuery = ref(''); const { t } = useI18n(); const createDialog = ref(null); @@ -138,6 +141,9 @@ const fetchResponses = (page = 1) => { if (selectedAssistant.value !== 'all') { filterParams.assistantId = selectedAssistant.value; } + if (searchQuery.value) { + filterParams.search = searchQuery.value; + } store.dispatch('captainResponses/get', filterParams); }; @@ -250,6 +256,10 @@ const handleAssistantFilterChange = assistant => { fetchResponses(); }; +const debouncedSearch = debounce(async () => { + fetchResponses(); +}, 500); + onMounted(() => { store.dispatch('captainAssistants/get'); fetchResponses(); @@ -292,34 +302,47 @@ onMounted(() => {