chore: update Captain documents filter UI (#14429)
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
<script setup>
|
||||
import { computed, ref } from 'vue';
|
||||
import { debounce } from '@chatwoot/utils';
|
||||
|
||||
import DocumentFiltersBar from 'dashboard/components-next/captain/assistant/DocumentFiltersBar.vue';
|
||||
|
||||
@@ -9,13 +8,9 @@ const emit = defineEmits(['change']);
|
||||
const source = ref('all');
|
||||
const status = ref(null);
|
||||
const sort = ref('recently_updated');
|
||||
const query = ref('');
|
||||
|
||||
const hasActiveFilters = computed(
|
||||
() =>
|
||||
source.value !== 'all' ||
|
||||
Boolean(status.value) ||
|
||||
Boolean(query.value.trim())
|
||||
() => source.value !== 'all' || Boolean(status.value)
|
||||
);
|
||||
|
||||
const buildParams = (page = 1) => {
|
||||
@@ -23,12 +18,10 @@ const buildParams = (page = 1) => {
|
||||
if (source.value !== 'all') params.source = source.value;
|
||||
if (status.value) params.filter = status.value;
|
||||
if (sort.value) params.sort = sort.value;
|
||||
if (query.value.trim()) params.searchKey = query.value.trim();
|
||||
return params;
|
||||
};
|
||||
|
||||
const emitChange = () => emit('change');
|
||||
const debouncedEmitChange = debounce(emitChange, 300);
|
||||
|
||||
const handleSourceSelect = sourceKey => {
|
||||
source.value = sourceKey;
|
||||
@@ -47,16 +40,10 @@ const handleSortSelect = sortKey => {
|
||||
emitChange();
|
||||
};
|
||||
|
||||
const handleSearch = value => {
|
||||
query.value = value;
|
||||
debouncedEmitChange();
|
||||
};
|
||||
|
||||
const reset = () => {
|
||||
source.value = 'all';
|
||||
status.value = null;
|
||||
sort.value = 'recently_updated';
|
||||
query.value = '';
|
||||
};
|
||||
|
||||
defineExpose({ buildParams, reset, hasActiveFilters });
|
||||
@@ -67,10 +54,8 @@ defineExpose({ buildParams, reset, hasActiveFilters });
|
||||
:active-source-filter="source"
|
||||
:active-status-filter="status"
|
||||
:active-sort="sort"
|
||||
:search-query="query"
|
||||
@select-source="handleSourceSelect"
|
||||
@select-status="handleStatusSelect"
|
||||
@select-sort="handleSortSelect"
|
||||
@search="handleSearch"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<script setup>
|
||||
import { computed, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { vOnClickOutside } from '@vueuse/components';
|
||||
|
||||
import Icon from 'dashboard/components-next/icon/Icon.vue';
|
||||
import Input from 'dashboard/components-next/input/Input.vue';
|
||||
import Button from 'dashboard/components-next/button/Button.vue';
|
||||
import DropdownMenu from 'dashboard/components-next/dropdown-menu/DropdownMenu.vue';
|
||||
|
||||
@@ -11,15 +11,9 @@ const props = defineProps({
|
||||
activeSourceFilter: { type: String, default: 'all' },
|
||||
activeStatusFilter: { type: String, default: null },
|
||||
activeSort: { type: String, default: 'recently_updated' },
|
||||
searchQuery: { type: String, default: '' },
|
||||
});
|
||||
|
||||
const emit = defineEmits([
|
||||
'selectSource',
|
||||
'selectStatus',
|
||||
'selectSort',
|
||||
'search',
|
||||
]);
|
||||
const emit = defineEmits(['selectSource', 'selectStatus', 'selectSort']);
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
@@ -121,45 +115,27 @@ const handleMenuAction = ({ action, value }) => {
|
||||
|
||||
<template>
|
||||
<div
|
||||
v-on-clickaway="closeMenu"
|
||||
class="flex flex-col gap-3 w-full lg:flex-row lg:items-center lg:justify-between"
|
||||
v-on-click-outside="closeMenu"
|
||||
class="inline-flex flex-wrap items-center gap-2 pt-2 w-fit"
|
||||
>
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<div v-for="menu in filterMenus" :key="menu.key" class="relative">
|
||||
<Button
|
||||
:icon="menu.selected.icon"
|
||||
slate
|
||||
outline
|
||||
:class="{ 'bg-n-slate-9/10': openMenu === menu.key }"
|
||||
@click="toggleMenu(menu.key)"
|
||||
>
|
||||
<span class="min-w-0 truncate">{{ menu.selected.label }}</span>
|
||||
<Icon icon="i-lucide-chevron-down" class="shrink-0 size-4" />
|
||||
</Button>
|
||||
<DropdownMenu
|
||||
v-if="openMenu === menu.key"
|
||||
:menu-items="menu.items"
|
||||
:class="menu.dropdownClass"
|
||||
class="top-full mt-2 ltr:left-0 rtl:right-0"
|
||||
@action="handleMenuAction"
|
||||
/>
|
||||
</div>
|
||||
<div v-for="menu in filterMenus" :key="menu.key" class="relative">
|
||||
<Button
|
||||
:icon="menu.selected.icon"
|
||||
slate
|
||||
size="sm"
|
||||
:class="{ 'bg-n-slate-9/10': openMenu === menu.key }"
|
||||
@click="toggleMenu(menu.key)"
|
||||
>
|
||||
<span class="min-w-0 truncate">{{ menu.selected.label }}</span>
|
||||
<Icon icon="i-lucide-chevron-down" class="shrink-0 size-4" />
|
||||
</Button>
|
||||
<DropdownMenu
|
||||
v-if="openMenu === menu.key"
|
||||
:menu-items="menu.items"
|
||||
:class="menu.dropdownClass"
|
||||
class="top-full mt-2 ltr:left-0 rtl:right-0"
|
||||
@action="handleMenuAction"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Input
|
||||
:model-value="searchQuery"
|
||||
type="search"
|
||||
:placeholder="t('CAPTAIN.DOCUMENTS.FILTERS.SEARCH_PLACEHOLDER')"
|
||||
:custom-input-class="['ltr:!pl-9 rtl:!pr-9']"
|
||||
class="w-full lg:max-w-72"
|
||||
@input="emit('search', $event.target.value)"
|
||||
>
|
||||
<template #prefix>
|
||||
<Icon
|
||||
icon="i-lucide-search"
|
||||
class="absolute size-4 text-n-slate-11 top-1/2 -translate-y-1/2 ltr:left-3 rtl:right-3"
|
||||
/>
|
||||
</template>
|
||||
</Input>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -7,11 +7,13 @@ import { FEATURE_FLAGS } from 'dashboard/featureFlags';
|
||||
import { useAccount } from 'dashboard/composables/useAccount';
|
||||
import { useAlert } from 'dashboard/composables';
|
||||
import { usePolicy } from 'dashboard/composables/usePolicy';
|
||||
import { debounce } from '@chatwoot/utils';
|
||||
|
||||
import DeleteDialog from 'dashboard/components-next/captain/pageComponents/DeleteDialog.vue';
|
||||
import DocumentCard from 'dashboard/components-next/captain/assistant/DocumentCard.vue';
|
||||
import DocumentFilter from 'dashboard/components-next/captain/assistant/DocumentFilter.vue';
|
||||
import DocumentBulkActions from 'dashboard/components-next/captain/assistant/DocumentBulkActions.vue';
|
||||
import Input from 'dashboard/components-next/input/Input.vue';
|
||||
import Policy from 'dashboard/components/policy.vue';
|
||||
import PageLayout from 'dashboard/components-next/captain/PageLayout.vue';
|
||||
import CaptainPaywall from 'dashboard/components-next/captain/pageComponents/Paywall.vue';
|
||||
@@ -73,6 +75,7 @@ const handleCreateDialogClose = () => {
|
||||
|
||||
const documentFilter = ref(null);
|
||||
const syncIntervalHours = ref(null);
|
||||
const searchQuery = ref('');
|
||||
|
||||
const currentAssistantId = () =>
|
||||
Number.isFinite(selectedAssistantId.value) ? selectedAssistantId.value : null;
|
||||
@@ -84,6 +87,8 @@ const buildDocumentFilterParams = (page = 1) => {
|
||||
};
|
||||
const assistantId = currentAssistantId();
|
||||
if (assistantId) filterParams.assistantId = assistantId;
|
||||
const trimmedQuery = searchQuery.value.trim();
|
||||
if (trimmedQuery) filterParams.searchKey = trimmedQuery;
|
||||
return filterParams;
|
||||
};
|
||||
|
||||
@@ -154,6 +159,11 @@ const onFiltersChanged = () => {
|
||||
fetchDocuments(1);
|
||||
};
|
||||
|
||||
const debouncedSearch = debounce(() => {
|
||||
bulkSelectedIds.value = new Set();
|
||||
fetchDocuments(1);
|
||||
}, 300);
|
||||
|
||||
const syncPollStartedAt = ref(null);
|
||||
|
||||
const hasDocumentsSyncing = computed(() =>
|
||||
@@ -284,13 +294,16 @@ const onCreateSuccess = () => {
|
||||
};
|
||||
|
||||
const hasActiveDocumentFilters = computed(
|
||||
() => documentFilter.value?.hasActiveFilters ?? false
|
||||
() =>
|
||||
(documentFilter.value?.hasActiveFilters ?? false) ||
|
||||
Boolean(searchQuery.value.trim())
|
||||
);
|
||||
|
||||
watch(
|
||||
selectedAssistantId,
|
||||
async () => {
|
||||
documentFilter.value?.reset();
|
||||
searchQuery.value = '';
|
||||
bulkSelectedIds.value = new Set();
|
||||
syncIntervalHours.value = null;
|
||||
stopSyncPolling();
|
||||
@@ -320,6 +333,22 @@ onUnmounted(() => {
|
||||
@update:current-page="onPageChange"
|
||||
@click="handleCreateDocument"
|
||||
>
|
||||
<template #search>
|
||||
<div
|
||||
v-if="bulkSelectedIds.size === 0"
|
||||
class="flex gap-3 justify-between w-full items-center"
|
||||
>
|
||||
<Input
|
||||
v-model="searchQuery"
|
||||
:placeholder="$t('CAPTAIN.DOCUMENTS.FILTERS.SEARCH_PLACEHOLDER')"
|
||||
class="max-w-64 min-w-0 w-full"
|
||||
size="sm"
|
||||
type="search"
|
||||
@input="debouncedSearch"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #subHeader>
|
||||
<Policy :permissions="['administrator']">
|
||||
<DocumentBulkActions
|
||||
|
||||
Reference in New Issue
Block a user