[UI] Table filter set (#12079)

* Save and load custom filter sets

* Tweak UI logic

* Adjust icons

* More refactoring

* Update UI docs

* Playwright tests

* Add docs image

* Fix image name

* Update docs/docs/concepts/user_interface.md

Co-authored-by: Matthias Mair <code@mjmair.com>

* Add CHANGELOG entry

---------

Co-authored-by: Matthias Mair <code@mjmair.com>
This commit is contained in:
Oliver
2026-06-04 22:19:28 +10:00
committed by GitHub
co-authored by Matthias Mair
parent 3dfd03fa89
commit 1b8217e8b3
8 changed files with 304 additions and 51 deletions
+54 -3
View File
@@ -1,6 +1,10 @@
import { useLocalStorage } from '@mantine/hooks';
import { useCallback, useEffect, useMemo } from 'react';
import type { FilterSetState, TableFilter } from '../types/Filters';
import type {
FilterSetState,
NamedFilterSet,
TableFilter
} from '../types/Filters';
export default function useFilterSet(
filterKey: string,
@@ -16,6 +20,16 @@ export default function useFilterSet(
getInitialValueInEffect: false
});
// Named filter set snapshots (saved to local storage, separate key)
const [storedNamedSets, setStoredNamedSets] = useLocalStorage<
NamedFilterSet[]
>({
key: `inventree-filtersets-${filterKey}`,
defaultValue: [],
sync: false,
getInitialValueInEffect: false
});
useEffect(() => {
if (storedFilters == null) {
setStoredFilters(initialFilters || []);
@@ -26,7 +40,6 @@ export default function useFilterSet(
return storedFilters ?? initialFilters ?? [];
}, [storedFilters, initialFilters]);
// Callback to clear all active filters from the table
const clearActiveFilters = useCallback(() => {
setStoredFilters([]);
}, []);
@@ -38,10 +51,48 @@ export default function useFilterSet(
[setStoredFilters]
);
const saveFilterSet = useCallback(
(name: string) => {
const snapshot = activeFilters.map(
({ name: n, value, displayValue }) => ({
name: n,
value,
displayValue
})
);
setStoredNamedSets((prev) => {
const without = (prev ?? []).filter((s) => s.name !== name);
return [...without, { name, filters: snapshot }];
});
},
[activeFilters, setStoredNamedSets]
);
const loadFilterSet = useCallback(
(name: string) => {
const saved = (storedNamedSets ?? []).find((s) => s.name === name);
if (saved) {
setStoredFilters(saved.filters as TableFilter[]);
}
},
[storedNamedSets, setStoredFilters]
);
const deleteFilterSet = useCallback(
(name: string) => {
setStoredNamedSets((prev) => (prev ?? []).filter((s) => s.name !== name));
},
[setStoredNamedSets]
);
return {
filterKey,
activeFilters,
setActiveFilters,
clearActiveFilters
clearActiveFilters,
savedFilterSets: storedNamedSets ?? [],
saveFilterSet,
loadFilterSet,
deleteFilterSet
};
}
+16
View File
@@ -57,6 +57,14 @@ export type TableFilter = {
multi?: boolean;
};
/*
* A named snapshot of a set of active filters, saved to local storage.
*/
export type NamedFilterSet = {
name: string;
filters: Pick<TableFilter, 'name' | 'value' | 'displayValue'>[];
};
/*
* Type definition for representing the state of a group of filters.
* These may be applied to a data view (e.g. table, calendar) to filter the displayed data.
@@ -65,10 +73,18 @@ export type TableFilter = {
* activeFilters: An array of active filters
* setActiveFilters: A function to set the active filters
* clearActiveFilters: A function to clear all active filters
* savedFilterSets: Named filter set snapshots persisted to local storage
* saveFilterSet: Save the current active filters under a given name
* loadFilterSet: Replace active filters with a previously saved named set
* deleteFilterSet: Remove a saved named filter set by name
*/
export type FilterSetState = {
filterKey: string;
activeFilters: TableFilter[];
setActiveFilters: (filters: TableFilter[]) => void;
clearActiveFilters: () => void;
savedFilterSets: NamedFilterSet[];
saveFilterSet: (name: string) => void;
loadFilterSet: (name: string) => void;
deleteFilterSet: (name: string) => void;
};