feat(grid): support searching filter columns
This commit is contained in:
parent
d50f1761d6
commit
3497df033f
|
|
@ -82,6 +82,7 @@ const queryStore = useQueryStore();
|
|||
const { openTableTarget } = useNavigationTargets({
|
||||
showFieldLineageDialog: ref(false),
|
||||
showDatabaseSearchDialog: ref(false),
|
||||
showDiagramDialog: ref(false),
|
||||
});
|
||||
const { toast } = useToast();
|
||||
const { isDark } = useTheme();
|
||||
|
|
|
|||
|
|
@ -1080,6 +1080,7 @@ const serverFilterValueByKey = ref<Map<string, CellValue>>(new Map());
|
|||
let serverFilterRequestId = 0;
|
||||
let serverFilterSearchTimer: ReturnType<typeof window.setTimeout> | undefined;
|
||||
const filterBuilderOpen = ref(false);
|
||||
const filterBuilderColumnSearch = ref("");
|
||||
const filterModeOptions: Array<{ value: FilterMode; labelKey: string }> = [
|
||||
{ value: "equals", labelKey: "grid.filterBuilderEquals" },
|
||||
{ value: "not-equals", labelKey: "grid.filterBuilderNotEquals" },
|
||||
|
|
@ -1092,6 +1093,12 @@ const filterModeOptions: Array<{ value: FilterMode; labelKey: string }> = [
|
|||
];
|
||||
const filterBuilderColumns = computed(() => props.tableMeta?.columns ?? []);
|
||||
const filterBuilderColumnOptions = computed(() => filterBuilderColumns.value.map((column) => column.name));
|
||||
const filteredFilterBuilderColumnOptions = computed(() => {
|
||||
const query = filterBuilderColumnSearch.value.trim().toLowerCase();
|
||||
if (!query) return filterBuilderColumnOptions.value;
|
||||
return filterBuilderColumnOptions.value.filter((columnName) => columnName.toLowerCase().includes(query));
|
||||
});
|
||||
const filterBuilderColumnSearchNavigationKeys = new Set(["Escape", "Tab", "ArrowUp", "ArrowDown", "Home", "End", "PageUp", "PageDown"]);
|
||||
const structuredFilterCacheKey = computed(() => props.cacheKey || [props.connectionId ?? "", props.database ?? "", props.context ?? "", props.tableMeta?.schema ?? "", props.tableMeta?.tableName ?? ""].join("\u0001"));
|
||||
const structuredFilterScopeKey = computed(() => [props.connectionId ?? "", props.database ?? "", props.schema ?? "", props.context ?? "", props.tableMeta?.schema ?? "", props.tableMeta?.tableName ?? "", filterBuilderColumnOptions.value.join("\0")].join("\u0001"));
|
||||
const structuredFilterRules = ref<StructuredFilterRule[]>([]);
|
||||
|
|
@ -1815,6 +1822,24 @@ function updateStructuredFilterRule(ruleId: string, patch: Partial<StructuredFil
|
|||
});
|
||||
}
|
||||
|
||||
function updateStructuredFilterRuleColumn(ruleId: string, columnName: string) {
|
||||
updateStructuredFilterRule(ruleId, { columnName });
|
||||
filterBuilderColumnSearch.value = "";
|
||||
}
|
||||
|
||||
function handleFilterBuilderColumnSearchKeydown(event: KeyboardEvent) {
|
||||
if (event.isComposing || event.key === "Process") {
|
||||
event.stopPropagation();
|
||||
return;
|
||||
}
|
||||
if (filterBuilderColumnSearchNavigationKeys.has(event.key)) return;
|
||||
if (event.ctrlKey || event.metaKey || event.altKey) return;
|
||||
if (event.key.length === 1 || event.key === "Backspace" || event.key === "Delete") {
|
||||
// Let text editing stay in the search box without breaking Select navigation keys.
|
||||
event.stopPropagation();
|
||||
}
|
||||
}
|
||||
|
||||
function resetStructuredFilters() {
|
||||
appliedStructuredWhereInput.value = "";
|
||||
structuredFilterRules.value = filterBuilderColumnOptions.value.length > 0 ? [defaultStructuredFilterRule()] : [];
|
||||
|
|
@ -1871,6 +1896,10 @@ async function applyStructuredFilters() {
|
|||
|
||||
watch([structuredFilterCacheKey, structuredFilterScopeKey], loadStructuredFilterStateForScope, { immediate: true });
|
||||
|
||||
watch(filterBuilderOpen, (open) => {
|
||||
if (!open) filterBuilderColumnSearch.value = "";
|
||||
});
|
||||
|
||||
watch(
|
||||
[structuredFilterRules, appliedStructuredWhereInput],
|
||||
() => {
|
||||
|
|
@ -8243,14 +8272,32 @@ const gridContextMenuItems = computed<ContextMenuItem[]>(() => {
|
|||
</Button>
|
||||
</div>
|
||||
<div class="grid grid-cols-[minmax(0,1.2fr)_minmax(0,1fr)_minmax(0,1.2fr)_auto] items-center gap-2">
|
||||
<Select :model-value="rule.columnName" :disabled="rule.disabled" :class="rule.disabled ? 'opacity-45' : ''" @update:model-value="(value: any) => updateStructuredFilterRule(rule.id, { columnName: String(value) })">
|
||||
<Select :model-value="rule.columnName" :disabled="rule.disabled" :class="rule.disabled ? 'opacity-45' : ''" @update:model-value="(value: any) => updateStructuredFilterRuleColumn(rule.id, String(value))">
|
||||
<SelectTrigger class="h-8 w-full min-w-0 overflow-hidden text-xs [&_[data-slot=select-value]]:min-w-0 [&_[data-slot=select-value]]:truncate">
|
||||
<SelectValue :placeholder="t('grid.filterBuilderColumn')" />
|
||||
</SelectTrigger>
|
||||
<SelectContent position="popper">
|
||||
<SelectItem v-for="columnName in filterBuilderColumnOptions" :key="columnName" :value="columnName">
|
||||
<SelectContent position="popper" class="max-h-72" :hide-scroll-buttons="true">
|
||||
<SelectItem v-for="columnName in filteredFilterBuilderColumnOptions" :key="columnName" :value="columnName">
|
||||
{{ columnName }}
|
||||
</SelectItem>
|
||||
<div v-if="filteredFilterBuilderColumnOptions.length === 0" class="px-2 py-2 text-xs text-muted-foreground">
|
||||
{{ t("grid.filterBuilderNoMatchingColumns") }}
|
||||
</div>
|
||||
<div class="sticky bottom-0 mt-1 flex items-center gap-1.5 border-t bg-popover px-2 py-1.5">
|
||||
<Search class="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
|
||||
<input
|
||||
v-model="filterBuilderColumnSearch"
|
||||
autocapitalize="off"
|
||||
autocomplete="off"
|
||||
autocorrect="off"
|
||||
spellcheck="false"
|
||||
class="h-7 min-w-0 flex-1 bg-transparent text-xs outline-none placeholder:text-muted-foreground"
|
||||
:placeholder="t('grid.filterBuilderSearchColumns')"
|
||||
@click.stop
|
||||
@keydown="handleFilterBuilderColumnSearchKeydown"
|
||||
@pointerdown.stop
|
||||
/>
|
||||
</div>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
|
|
|
|||
|
|
@ -16,17 +16,19 @@ const props = withDefaults(
|
|||
class?: HTMLAttributes["class"];
|
||||
disablePortal?: boolean;
|
||||
disableOutsidePointerEvents?: boolean;
|
||||
hideScrollButtons?: boolean;
|
||||
}
|
||||
>(),
|
||||
{
|
||||
position: "popper",
|
||||
disablePortal: false,
|
||||
disableOutsidePointerEvents: true,
|
||||
hideScrollButtons: false,
|
||||
},
|
||||
);
|
||||
const emits = defineEmits<SelectContentEmits>();
|
||||
|
||||
const delegatedProps = reactiveOmit(props, "class", "disablePortal");
|
||||
const delegatedProps = reactiveOmit(props, "class", "disablePortal", "hideScrollButtons");
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
||||
</script>
|
||||
|
|
@ -45,11 +47,11 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
|||
)
|
||||
"
|
||||
>
|
||||
<SelectScrollUpButton />
|
||||
<SelectScrollUpButton v-if="!hideScrollButtons" />
|
||||
<SelectViewport :data-position="position" :class="cn('data-[position=popper]:h-[var(--reka-select-trigger-height)] data-[position=popper]:w-full')">
|
||||
<slot />
|
||||
</SelectViewport>
|
||||
<SelectScrollDownButton />
|
||||
<SelectScrollDownButton v-if="!hideScrollButtons" />
|
||||
</SelectContent>
|
||||
</SelectPortal>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -719,6 +719,8 @@ export default {
|
|||
filterBuilderSummary: "{count} rules",
|
||||
filterBuilderAddRule: "Add rule",
|
||||
filterBuilderColumn: "Column",
|
||||
filterBuilderSearchColumns: "Search columns...",
|
||||
filterBuilderNoMatchingColumns: "No matching columns",
|
||||
filterBuilderValue: "Value",
|
||||
filterBuilderNoValue: "No value needed",
|
||||
filterBuilderEmpty: "Add a rule to start filtering rows.",
|
||||
|
|
|
|||
|
|
@ -919,6 +919,8 @@ export default withEnglishFallback({
|
|||
},
|
||||
sortUnsupported: "Este SQL no admite ordenamiento del resultado completo. Intenta con una consulta SELECT simple.",
|
||||
truncatedHint: "Los resultados están limitados a 10.000 filas. Usa LIMIT/OFFSET en tu consulta para paginar.",
|
||||
filterBuilderSearchColumns: "Buscar columnas...",
|
||||
filterBuilderNoMatchingColumns: "Sin columnas coincidentes",
|
||||
},
|
||||
exportProgress: {
|
||||
title: "Exportando datos de la tabla",
|
||||
|
|
|
|||
|
|
@ -917,6 +917,8 @@ export default withEnglishFallback({
|
|||
},
|
||||
sortUnsupported: "Questo SQL non supporta l'ordinamento sull'intero risultato. Riprova con una query SELECT semplice.",
|
||||
truncatedHint: "Risultati troncati a {count} righe. Usa la paginazione a piè di pagina o regola le righe per pagina.",
|
||||
filterBuilderSearchColumns: "Cerca campi...",
|
||||
filterBuilderNoMatchingColumns: "Nessun campo corrispondente",
|
||||
},
|
||||
exportProgress: {
|
||||
title: "Esportazione Dati Tabella",
|
||||
|
|
|
|||
|
|
@ -917,6 +917,8 @@ export default withEnglishFallback({
|
|||
renderMode: "レンダーモード",
|
||||
undoChange: "変更を元に戻す",
|
||||
redoChange: "変更をやり直す",
|
||||
filterBuilderSearchColumns: "フィールドを検索...",
|
||||
filterBuilderNoMatchingColumns: "一致するフィールドがありません",
|
||||
},
|
||||
exportProgress: {
|
||||
title: "テーブルデータをエクスポート中",
|
||||
|
|
|
|||
|
|
@ -918,6 +918,8 @@ export default withEnglishFallback({
|
|||
},
|
||||
sortUnsupported: "Este SQL não suporta a ordenação do resultado completo. Tente novamente com uma única consulta SELECT.",
|
||||
truncatedHint: "Resultados truncados em {count} linhas. Use a paginação no rodapé ou ajuste as linhas por página.",
|
||||
filterBuilderSearchColumns: "Pesquisar campos...",
|
||||
filterBuilderNoMatchingColumns: "Nenhum campo correspondente",
|
||||
},
|
||||
exportProgress: {
|
||||
title: "Exportando Dados da Tabela",
|
||||
|
|
|
|||
|
|
@ -721,6 +721,8 @@ export default withEnglishFallback({
|
|||
filterBuilderSummary: "{count} 条规则",
|
||||
filterBuilderAddRule: "新增条件",
|
||||
filterBuilderColumn: "字段",
|
||||
filterBuilderSearchColumns: "搜索字段...",
|
||||
filterBuilderNoMatchingColumns: "没有匹配的字段",
|
||||
filterBuilderValue: "值",
|
||||
filterBuilderNoValue: "无需输入值",
|
||||
filterBuilderEmpty: "新增一条条件后即可开始筛选。",
|
||||
|
|
|
|||
|
|
@ -918,6 +918,8 @@ export default withEnglishFallback({
|
|||
},
|
||||
sortUnsupported: "目前 SQL 不支援完整排序,請改為單條 SELECT 查詢後再嘗試。",
|
||||
truncatedHint: "結果已截斷,僅顯示前 {count} 行。可經由底部分頁繼續載入,或調整每頁行數。",
|
||||
filterBuilderSearchColumns: "搜尋欄位...",
|
||||
filterBuilderNoMatchingColumns: "沒有匹配的欄位",
|
||||
},
|
||||
exportProgress: {
|
||||
title: "匯出資料表資料",
|
||||
|
|
|
|||
Loading…
Reference in New Issue