refactor(grid): extract local filter value logic
This commit is contained in:
3 files changed
+110
-46
No files matched your search
@@ -377,7 +377,7 @@ import { reserveDataGridHeaderLine } from "@/lib/dataGrid/dataGridHeaderLayout";
|
||||
import { buildColumnIndexMap, columnIndexColorClass, columnIndexMetadataRequestCurrent, columnIndexNameKey, columnIndexTableIdentity, type ColumnIndexKind } from "@/lib/dataGrid/dataGridColumnIndexIcon";
|
||||
import { supportsTableStructureEditing } from "@/lib/database/databaseCapabilities";
|
||||
import { rememberDataGridConditionHistory } from "@/lib/dataGrid/dataGridConditionHistory";
|
||||
import { restoreDataGridLocalColumnFilters, serializeDataGridLocalColumnFilters } from "@/lib/dataGrid/dataGridLocalColumnFilterState";
|
||||
import { buildDataGridLocalFilterOptions, dataGridLocalFilterKey, dataGridLocalFilterLabel, restoreDataGridLocalColumnFilters, rowMatchesDataGridLocalColumnFilters, serializeDataGridLocalColumnFilters, type DataGridLocalFilterOption } from "@/lib/dataGrid/dataGridLocalColumnFilterState";
|
||||
import { effectiveDatabaseTypeForConnection, gaussdbCountQueryDopHint } from "@/lib/database/jdbcDialect";
|
||||
import { mongoCollectionSupportsIndexes, supportsMongoIndexMutations } from "@/lib/mongo/mongoCapabilities";
|
||||
import { refreshLoadedMongoIndexes } from "@/lib/mongo/mongoIndexMetadata";
|
||||
@@ -1061,12 +1061,7 @@ const conditionHistoryScope = computed(() => ({
|
||||
tableName: props.tableMeta?.tableName,
|
||||
}));
|
||||
type LocalFilterMode = "local" | "server";
|
||||
type LocalFilterOption = {
|
||||
key: string;
|
||||
label: string;
|
||||
count: number | null;
|
||||
value: CellValue;
|
||||
};
|
||||
type LocalFilterOption = DataGridLocalFilterOption;
|
||||
|
||||
type LocalColumnFilterDraft = {
|
||||
columnIndex: number;
|
||||
@@ -1300,14 +1295,11 @@ const savedCustomFormatters = computed(() => {
|
||||
});
|
||||
|
||||
function localFilterKey(value: CellValue): string {
|
||||
if (value === null) return "__dbx_null__";
|
||||
if (typeof value === "boolean") return `bool:${value}`;
|
||||
if (typeof value === "number") return `num:${value}`;
|
||||
return `str:${String(value)}`;
|
||||
return dataGridLocalFilterKey(value);
|
||||
}
|
||||
|
||||
function localFilterLabel(value: CellValue, columnIndex: number): string {
|
||||
return value === null ? "NULL" : formatCellCached(value, columnIndex);
|
||||
return dataGridLocalFilterLabel(value, columnIndex, formatCellCached);
|
||||
}
|
||||
|
||||
function localFilterActive(colIdx: number): boolean {
|
||||
@@ -1345,9 +1337,7 @@ const localFilterSummaries = computed(() =>
|
||||
);
|
||||
|
||||
function rowMatchesLocalColumnFilters(data: CellValue[]): boolean {
|
||||
const activeEntries = Object.entries(localColumnFilters.value).filter(([, selected]) => selected.size > 0);
|
||||
if (activeEntries.length === 0) return true;
|
||||
return activeEntries.every(([columnIndex, selected]) => selected.has(localFilterKey(data[Number(columnIndex)] ?? null)));
|
||||
return rowMatchesDataGridLocalColumnFilters(data, localColumnFilters.value);
|
||||
}
|
||||
|
||||
const localFilteredRows = computed(() => {
|
||||
@@ -1366,36 +1356,12 @@ const localFilteredRows = computed(() => {
|
||||
});
|
||||
|
||||
function buildLocalFilterOptions(columnIndex: number): LocalFilterOption[] {
|
||||
const byKey = new Map<string, LocalFilterOption>();
|
||||
const addValue = (value: CellValue) => {
|
||||
const key = localFilterKey(value);
|
||||
const current = byKey.get(key);
|
||||
if (current) {
|
||||
current.count = (current.count ?? 0) + 1;
|
||||
} else {
|
||||
byKey.set(key, {
|
||||
key,
|
||||
label: localFilterLabel(value, columnIndex),
|
||||
count: 1,
|
||||
value,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
for (const [sourceIndex, row] of props.result.rows.entries()) {
|
||||
addValue(rowDataWithChanges(row, sourceIndex)[columnIndex] ?? null);
|
||||
}
|
||||
for (const row of newRows.value) {
|
||||
addValue(row[columnIndex] ?? null);
|
||||
}
|
||||
|
||||
return [...byKey.values()].sort((a, b) => {
|
||||
if (a.value === null && b.value !== null) return -1;
|
||||
if (a.value !== null && b.value === null) return 1;
|
||||
return a.label.localeCompare(b.label, undefined, {
|
||||
numeric: true,
|
||||
sensitivity: "base",
|
||||
});
|
||||
return buildDataGridLocalFilterOptions({
|
||||
rows: props.result.rows,
|
||||
newRows: newRows.value,
|
||||
columnIndex,
|
||||
getRowData: rowDataWithChanges,
|
||||
formatValue: formatCellCached,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+27
-1
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { restoreDataGridLocalColumnFilters, serializeDataGridLocalColumnFilters } from "@/lib/dataGrid/dataGridLocalColumnFilterState";
|
||||
import { buildDataGridLocalFilterOptions, dataGridLocalFilterKey, restoreDataGridLocalColumnFilters, rowMatchesDataGridLocalColumnFilters, serializeDataGridLocalColumnFilters } from "@/lib/dataGrid/dataGridLocalColumnFilterState";
|
||||
|
||||
describe("data grid local column filter state", () => {
|
||||
it("round-trips selected values so a remounted grid restores its filters", () => {
|
||||
@@ -27,4 +27,30 @@ describe("data grid local column filter state", () => {
|
||||
|
||||
expect(restored).toEqual({});
|
||||
});
|
||||
|
||||
it("builds stable keys and filters rows across multiple columns", () => {
|
||||
expect(dataGridLocalFilterKey(null)).toBe("__dbx_null__");
|
||||
expect(dataGridLocalFilterKey(true)).toBe("bool:true");
|
||||
expect(dataGridLocalFilterKey(42)).toBe("num:42");
|
||||
expect(dataGridLocalFilterKey("active")).toBe("str:active");
|
||||
|
||||
expect(rowMatchesDataGridLocalColumnFilters(["active", 42], { 0: new Set(["str:active"]), 1: new Set(["num:42"]) })).toBe(true);
|
||||
expect(rowMatchesDataGridLocalColumnFilters(["active", 7], { 0: new Set(["str:active"]), 1: new Set(["num:42"]) })).toBe(false);
|
||||
});
|
||||
|
||||
it("deduplicates local filter options and sorts null first", () => {
|
||||
const options = buildDataGridLocalFilterOptions({
|
||||
rows: [{ values: ["beta"] }, { values: [null] }, { values: ["beta"] }],
|
||||
newRows: [["alpha"]],
|
||||
columnIndex: 0,
|
||||
getRowData: (row) => row.values,
|
||||
formatValue: (value) => String(value),
|
||||
});
|
||||
|
||||
expect(options).toEqual([
|
||||
{ key: "__dbx_null__", label: "NULL", count: 1, value: null },
|
||||
{ key: "str:alpha", label: "alpha", count: 1, value: "alpha" },
|
||||
{ key: "str:beta", label: "beta", count: 2, value: "beta" },
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,77 @@
|
||||
import type { CellValue } from "./cellValue";
|
||||
|
||||
export type SerializedDataGridLocalColumnFilters = Record<string, string[]>;
|
||||
|
||||
export type DataGridLocalFilterOption = {
|
||||
key: string;
|
||||
label: string;
|
||||
count: number | null;
|
||||
value: CellValue;
|
||||
};
|
||||
|
||||
export function dataGridLocalFilterKey(value: CellValue): string {
|
||||
if (value === null) return "__dbx_null__";
|
||||
if (typeof value === "boolean") return `bool:${value}`;
|
||||
if (typeof value === "number") return `num:${value}`;
|
||||
return `str:${String(value)}`;
|
||||
}
|
||||
|
||||
export function dataGridLocalFilterLabel(value: CellValue, columnIndex: number, formatValue: (value: CellValue, columnIndex: number) => string): string {
|
||||
return value === null ? "NULL" : formatValue(value, columnIndex);
|
||||
}
|
||||
|
||||
export function rowMatchesDataGridLocalColumnFilters(data: readonly CellValue[], filters: Readonly<Record<number, ReadonlySet<string>>>): boolean {
|
||||
const activeEntries = Object.entries(filters).filter(([, selected]) => selected.size > 0);
|
||||
if (activeEntries.length === 0) return true;
|
||||
return activeEntries.every(([columnIndex, selected]) => selected.has(dataGridLocalFilterKey(data[Number(columnIndex)] ?? null)));
|
||||
}
|
||||
|
||||
export function buildDataGridLocalFilterOptions<TRow>({
|
||||
rows,
|
||||
newRows,
|
||||
columnIndex,
|
||||
getRowData,
|
||||
formatValue,
|
||||
}: {
|
||||
rows: readonly TRow[];
|
||||
newRows: readonly (readonly CellValue[])[];
|
||||
columnIndex: number;
|
||||
getRowData: (row: TRow, sourceIndex: number) => readonly CellValue[];
|
||||
formatValue: (value: CellValue, columnIndex: number) => string;
|
||||
}): DataGridLocalFilterOption[] {
|
||||
const byKey = new Map<string, DataGridLocalFilterOption>();
|
||||
const addValue = (value: CellValue) => {
|
||||
const key = dataGridLocalFilterKey(value);
|
||||
const current = byKey.get(key);
|
||||
if (current) {
|
||||
current.count = (current.count ?? 0) + 1;
|
||||
} else {
|
||||
byKey.set(key, {
|
||||
key,
|
||||
label: dataGridLocalFilterLabel(value, columnIndex, formatValue),
|
||||
count: 1,
|
||||
value,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
for (const [sourceIndex, row] of rows.entries()) {
|
||||
addValue(getRowData(row, sourceIndex)[columnIndex] ?? null);
|
||||
}
|
||||
for (const row of newRows) {
|
||||
addValue(row[columnIndex] ?? null);
|
||||
}
|
||||
|
||||
return [...byKey.values()].sort((a, b) => {
|
||||
if (a.value === null && b.value !== null) return -1;
|
||||
if (a.value !== null && b.value === null) return 1;
|
||||
return a.label.localeCompare(b.label, undefined, {
|
||||
numeric: true,
|
||||
sensitivity: "base",
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function restoreDataGridLocalColumnFilters(serialized: SerializedDataGridLocalColumnFilters | undefined, columnCount: number): Record<number, Set<string>> {
|
||||
if (!serialized || typeof serialized !== "object") return {};
|
||||
|
||||
|
||||
Reference in new issue
Block a user