mirror of
https://github.com/hansjone/netx.git
synced 2026-10-16 15:23:21 +08:00
feat: unify network and topology table column filters
Replace top filter bars with shared Excel-style column menus, fix select-all and modal interactions, and use plain dropdown indicators. Apply exact-value filters before pagination and export. Isolate pytest database state and cover filter behavior.
This commit is contained in:
parent
ed820126df
commit
3c9e0efd2b
32 changed files with 1429 additions and 645 deletions
25
web/src/hooks/useLocalTableColumnFilters.tsx
Normal file
25
web/src/hooks/useLocalTableColumnFilters.tsx
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import { useState } from "react";
|
||||
import { ColumnFilterMenu } from "../components/ui/ColumnFilterMenu";
|
||||
import type { TableColumnFilters } from "../types/tableFilters";
|
||||
|
||||
/** For detail tables whose complete displayed dataset is already in memory. */
|
||||
export function useLocalTableColumnFilters<T>(scope: string, rows: T[], values: (row: T) => Record<string, string>) {
|
||||
const [filters, setFilters] = useState<TableColumnFilters>({});
|
||||
return {
|
||||
items: rows.filter((row) => Object.entries(filters).every(([field, filter]) => {
|
||||
if (!filter) return true;
|
||||
const included = filter.values.includes(values(row)[field] ?? "");
|
||||
return filter.mode === "include" ? included : !included;
|
||||
})),
|
||||
reset: () => setFilters({}),
|
||||
column: (field: string, label: string) => (
|
||||
<span className="ne-column-heading">
|
||||
{label}
|
||||
<ColumnFilterMenu label={label} value={filters[field] ?? null}
|
||||
queryKey={["localTableFilterOptions", scope, field, JSON.stringify(rows.map((row) => values(row)[field] ?? ""))]}
|
||||
loadOptions={() => Promise.resolve([...new Set(rows.map((row) => values(row)[field] ?? ""))].sort())}
|
||||
onApply={(value) => setFilters((current) => ({ ...current, [field]: value }))} />
|
||||
</span>
|
||||
),
|
||||
};
|
||||
}
|
||||
34
web/src/hooks/useTableColumnFilters.tsx
Normal file
34
web/src/hooks/useTableColumnFilters.tsx
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
import { useState } from "react";
|
||||
import { ColumnFilterMenu } from "../components/ui/ColumnFilterMenu";
|
||||
import { fetchTableFilterOptions } from "../services/api/tableFilters";
|
||||
import type { ColumnValueFilter, TableColumnFilters } from "../types/tableFilters";
|
||||
|
||||
export function useTableColumnFilters(endpoint: string, onChange: () => void, scope: Record<string, string> = {}) {
|
||||
const [filters, setFilters] = useState<TableColumnFilters>({});
|
||||
const [currentEndpoint, setCurrentEndpoint] = useState(endpoint);
|
||||
if (currentEndpoint !== endpoint) {
|
||||
setCurrentEndpoint(endpoint);
|
||||
setFilters({});
|
||||
}
|
||||
const scopeKey = JSON.stringify(scope);
|
||||
const setFilter = (field: string, value: ColumnValueFilter) => {
|
||||
setFilters((current) => ({ ...current, [field]: value }));
|
||||
onChange();
|
||||
};
|
||||
return {
|
||||
filters,
|
||||
key: JSON.stringify(filters),
|
||||
active: Object.values(filters).some(Boolean),
|
||||
setFilter,
|
||||
reset: () => { setFilters({}); onChange(); },
|
||||
column: (field: string, label: string, formatValue?: (value: string) => string) => (
|
||||
<span className="ne-column-heading">
|
||||
{label}
|
||||
<ColumnFilterMenu label={label} value={filters[field] ?? null}
|
||||
queryKey={["tableFilterOptions", endpoint, scopeKey, field]}
|
||||
loadOptions={() => fetchTableFilterOptions(endpoint, field, scope)}
|
||||
formatValue={formatValue} onApply={(value) => setFilter(field, value)} />
|
||||
</span>
|
||||
),
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue