Merge commit '3393ef4e65267f4f8c5a5a87798553dbe0160a39' into block-notes

This commit is contained in:
Oliver Walters
2026-06-05 12:57:45 +00:00
87 changed files with 1730 additions and 350 deletions
+27
View File
@@ -0,0 +1,27 @@
import { ActionIcon, Badge, Group, Paper } from '@mantine/core';
import { IconTag } from '@tabler/icons-react';
export default function TagsList({
tags
}: Readonly<{
tags: string[];
}>) {
if (!tags || tags.length === 0) {
return null;
}
return (
<Paper p='xs' shadow='xs' withBorder>
<Group gap='xs'>
<ActionIcon size='sm' variant='transparent'>
<IconTag />
</ActionIcon>
{tags.map((tag: string) => (
<Badge key={tag} variant='outline' size='sm'>
{tag}
</Badge>
))}
</Group>
</Paper>
);
}
+1
View File
@@ -260,6 +260,7 @@ export enum ApiEndpoints {
config_list = 'admin/config/',
parameter_list = 'parameter/',
parameter_template_list = 'parameter/template/',
tag_list = 'tag/',
// Internal system things
system_internal_trace_end = 'system-internal/observability/end'
+8 -2
View File
@@ -118,8 +118,8 @@ export const ModelInformationDict: ModelDict = {
icon: 'history'
},
build: {
label: () => t`Build`,
label_multiple: () => t`Builds`,
label: () => t`Build Order`,
label_multiple: () => t`Build Orders`,
url_overview: '/manufacturing/index/buildorders/',
url_detail: '/manufacturing/build-order/:pk/',
api_endpoint: ApiEndpoints.build_order_list,
@@ -319,5 +319,11 @@ export const ModelInformationDict: ModelDict = {
url_overview: '/settings/admin/errors',
url_detail: '/settings/admin/errors/:pk/',
icon: 'exclamation'
},
tag: {
label: () => t`Tag`,
label_multiple: () => t`Tags`,
api_endpoint: ApiEndpoints.tag_list,
icon: 'tag'
}
};
+2 -1
View File
@@ -38,7 +38,8 @@ export enum ModelType {
contenttype = 'contenttype',
selectionlist = 'selectionlist',
selectionentry = 'selectionentry',
error = 'error'
error = 'error',
tag = 'tag'
}
export enum PluginPanelKey {
+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
};
}
+1
View File
@@ -110,6 +110,7 @@ export { ProgressBar } from './components/ProgressBar';
export { PassFailButton, YesNoButton } from './components/YesNoButton';
export { SearchInput } from './components/SearchInput';
export { TableColumnSelect } from './components/TableColumnSelect';
export { default as TagsList } from './components/TagsList';
export { default as InvenTreeTable } from './components/InvenTreeTable';
export {
RowViewAction,
+19
View File
@@ -41,6 +41,7 @@ export type TableFilter = {
name: string;
label?: string;
description?: string;
placeholder?: string;
type?: TableFilterType;
choices?: TableFilterChoice[];
choiceFunction?: () => TableFilterChoice[];
@@ -52,6 +53,16 @@ export type TableFilter = {
apiFilter?: Record<string, any>;
model?: ModelType;
modelRenderer?: (instance: any) => string;
transform?: (item: any) => TableFilterChoice;
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'>[];
};
/*
@@ -62,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;
};
+2 -1
View File
@@ -99,7 +99,8 @@ export type ApiFormFieldType = {
| 'file upload'
| 'nested object'
| 'dependent field'
| 'table';
| 'table'
| 'tags';
api_url?: string;
pk_field?: string;
model?: ModelType;