@inventreedb/ui 1.4.0 → 1.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/.vite/manifest.json +40 -1
  3. package/dist/_virtual/dynamic-import-helper.js +20 -0
  4. package/dist/_virtual/dynamic-import-helper.js.map +1 -0
  5. package/dist/components/Boundary.js +5 -4
  6. package/dist/components/Boundary.js.map +1 -1
  7. package/dist/components/TableColumnSelect.js +1 -1
  8. package/dist/components/TableColumnSelect.js.map +1 -1
  9. package/dist/components/TagsList.d.ts +3 -0
  10. package/dist/components/TagsList.js +21 -0
  11. package/dist/components/TagsList.js.map +1 -0
  12. package/dist/enums/ApiEndpoints.d.ts +15 -4
  13. package/dist/enums/ApiEndpoints.js +15 -4
  14. package/dist/enums/ApiEndpoints.js.map +1 -1
  15. package/dist/enums/ModelInformation.d.ts +1 -0
  16. package/dist/enums/ModelInformation.js +54 -2
  17. package/dist/enums/ModelInformation.js.map +1 -1
  18. package/dist/enums/ModelType.d.ts +4 -1
  19. package/dist/enums/ModelType.js +3 -0
  20. package/dist/enums/ModelType.js.map +1 -1
  21. package/dist/enums/Roles.d.ts +1 -0
  22. package/dist/enums/Roles.js +1 -0
  23. package/dist/enums/Roles.js.map +1 -1
  24. package/dist/functions/Navigation.js +1 -1
  25. package/dist/functions/Navigation.js.map +1 -1
  26. package/dist/hooks/UseFilterSet.js +38 -1
  27. package/dist/hooks/UseFilterSet.js.map +1 -1
  28. package/dist/index.d.ts +3 -0
  29. package/dist/index.js +16 -10
  30. package/dist/index.js.map +1 -1
  31. package/dist/node_modules/@tabler/icons-react/dist/esm/icons/IconTag.js +14 -0
  32. package/dist/node_modules/@tabler/icons-react/dist/esm/icons/IconTag.js.map +1 -0
  33. package/dist/plugin/InventreeHmrPlugin.d.ts +8 -0
  34. package/dist/plugin/InventreeHmrPlugin.js +21 -0
  35. package/dist/plugin/InventreeHmrPlugin.js.map +1 -0
  36. package/dist/plugin/LocalizedComponent.d.ts +11 -0
  37. package/dist/plugin/LocalizedComponent.js +57 -0
  38. package/dist/plugin/LocalizedComponent.js.map +1 -0
  39. package/dist/types/Filters.d.ts +11 -0
  40. package/dist/types/Forms.d.ts +4 -1
  41. package/dist/types/Plugins.js +1 -1
  42. package/dist/types/Rendering.d.ts +1 -0
  43. package/dist/types/Tables.d.ts +4 -2
  44. package/lib/components/Boundary.tsx +7 -3
  45. package/lib/components/TableColumnSelect.tsx +1 -1
  46. package/lib/components/TagsList.tsx +27 -0
  47. package/lib/enums/ApiEndpoints.tsx +18 -4
  48. package/lib/enums/ModelInformation.tsx +25 -2
  49. package/lib/enums/ModelType.tsx +4 -1
  50. package/lib/enums/Roles.tsx +3 -0
  51. package/lib/functions/Navigation.tsx +1 -1
  52. package/lib/hooks/UseFilterSet.tsx +54 -3
  53. package/lib/index.ts +5 -0
  54. package/lib/plugin/InventreeHmrPlugin.tsx +40 -0
  55. package/lib/plugin/LocalizedComponent.tsx +87 -0
  56. package/lib/types/Filters.tsx +19 -0
  57. package/lib/types/Forms.tsx +5 -1
  58. package/lib/types/Rendering.tsx +1 -0
  59. package/lib/types/Tables.tsx +5 -2
  60. package/package.json +2 -2
@@ -11,6 +11,7 @@ export interface InstanceRenderInterface {
11
11
  link?: boolean;
12
12
  navigate?: any;
13
13
  showSecondary?: boolean;
14
+ showHover?: boolean;
14
15
  extra?: Record<string, any>;
15
16
  }
16
17
  type EnumDictionary<T extends string | symbol | number, U> = {
@@ -48,7 +48,7 @@ export type TableState = {
48
48
  * @param editable - Whether the value of this column can be edited
49
49
  * @param definition - Optional field definition for the column
50
50
  * @param render - A custom render function
51
- * @param filter - A custom filter function
51
+ * @param filter - Filter name (string) to look up from tableFilters and attach an inline icon, or a custom render function for the filter popover
52
52
  * @param filtering - Whether the column is filterable
53
53
  * @param width - The width of the column
54
54
  * @param minWidth - The minimum width of the column
@@ -73,7 +73,9 @@ export type TableColumnProps<T = any> = {
73
73
  editable?: boolean;
74
74
  definition?: ApiFormFieldType;
75
75
  render?: (record: T, index?: number) => any;
76
- filter?: any;
76
+ filter?: string | string[] | (({ close }: {
77
+ close: () => void;
78
+ }) => ReactNode);
77
79
  filtering?: boolean;
78
80
  width?: number;
79
81
  minWidth?: string | number;
@@ -1,5 +1,5 @@
1
1
  import { t } from '@lingui/core/macro';
2
- import { Alert, Stack } from '@mantine/core';
2
+ import { Alert, Stack, Text } from '@mantine/core';
3
3
  import { ErrorBoundary, type FallbackRender } from '@sentry/react';
4
4
  import { IconExclamationCircle } from '@tabler/icons-react';
5
5
  import { type ReactNode, useCallback } from 'react';
@@ -14,8 +14,12 @@ export function DefaultFallback({
14
14
  title={`INVE-E17: ${t`Error rendering component`}: ${title}`}
15
15
  >
16
16
  <Stack gap='xs'>
17
- {t`An error occurred while rendering this component. Refer to the console for more information.`}
18
- {t`Try reloading the page, or contact your administrator if the problem persists.`}
17
+ <Text size='sm'>
18
+ {t`An error occurred while rendering this component. Refer to the console for more information.`}
19
+ </Text>
20
+ <Text size='sm'>
21
+ {t`Try reloading the page, or contact your administrator if the problem persists.`}
22
+ </Text>
19
23
  </Stack>
20
24
  </Alert>
21
25
  );
@@ -23,7 +23,7 @@ export function TableColumnSelect({
23
23
  <Menu.Label>{t`Select Columns`}</Menu.Label>
24
24
  <Divider />
25
25
  {columns
26
- .filter((col) => col.switchable ?? true)
26
+ .filter((col) => (col.switchable ?? true) && !col.propHidden)
27
27
  .map((col) => (
28
28
  <Menu.Item key={col.accessor}>
29
29
  <Checkbox
@@ -0,0 +1,27 @@
1
+ import { ActionIcon, Badge, Group, Paper } from '@mantine/core';
2
+ import { IconTag } from '@tabler/icons-react';
3
+
4
+ export default function TagsList({
5
+ tags
6
+ }: Readonly<{
7
+ tags: string[];
8
+ }>) {
9
+ if (!tags || tags.length === 0) {
10
+ return null;
11
+ }
12
+
13
+ return (
14
+ <Paper p='xs' shadow='xs' withBorder>
15
+ <Group gap='xs'>
16
+ <ActionIcon size='sm' variant='transparent'>
17
+ <IconTag />
18
+ </ActionIcon>
19
+ {tags.map((tag: string) => (
20
+ <Badge key={tag} variant='outline' size='sm'>
21
+ {tag}
22
+ </Badge>
23
+ ))}
24
+ </Group>
25
+ </Paper>
26
+ );
27
+ }
@@ -12,12 +12,13 @@ export enum ApiEndpoints {
12
12
  // User API endpoints
13
13
  user_list = 'user/',
14
14
  user_set_password = 'user/:id/set-password/',
15
- user_me = 'user/me/',
16
- user_profile = 'user/profile/',
17
- user_roles = 'user/roles/',
18
- user_token = 'user/token/',
19
15
  user_tokens = 'user/tokens/',
20
16
  user_simple_login = 'email/generate/',
17
+ // Individual user endpoints
18
+ user_me_profile = 'user/me/profile/',
19
+ user_me_roles = 'user/me/roles/',
20
+ user_me_token = 'user/me/token/',
21
+ user_me = 'user/me/',
21
22
 
22
23
  // User auth endpoints
23
24
  auth_base = '/auth/',
@@ -181,6 +182,7 @@ export enum ApiEndpoints {
181
182
  sales_order_complete = 'order/so/:id/complete/',
182
183
  sales_order_allocate = 'order/so/:id/allocate/',
183
184
  sales_order_allocate_serials = 'order/so/:id/allocate-serials/',
185
+ sales_order_auto_allocate = 'order/so/:id/auto-allocate/',
184
186
 
185
187
  sales_order_line_list = 'order/so-line/',
186
188
  sales_order_extra_line_list = 'order/so-extra-line/',
@@ -198,6 +200,17 @@ export enum ApiEndpoints {
198
200
  return_order_line_list = 'order/ro-line/',
199
201
  return_order_extra_line_list = 'order/ro-extra-line/',
200
202
 
203
+ transfer_order_list = 'order/transfer-order/',
204
+ transfer_order_issue = 'order/transfer-order/:id/issue/',
205
+ transfer_order_hold = 'order/transfer-order/:id/hold/',
206
+ transfer_order_cancel = 'order/transfer-order/:id/cancel/',
207
+ transfer_order_complete = 'order/transfer-order/:id/complete/',
208
+ transfer_order_allocate = 'order/transfer-order/:id/allocate/',
209
+ transfer_order_allocate_serials = 'order/transfer-order/:id/allocate-serials/',
210
+
211
+ transfer_order_line_list = 'order/transfer-order-line/',
212
+ transfer_order_allocation_list = 'order/transfer-order-allocation/',
213
+
201
214
  // Template API endpoints
202
215
  label_list = 'label/template/',
203
216
  label_print = 'label/print/',
@@ -246,6 +259,7 @@ export enum ApiEndpoints {
246
259
  config_list = 'admin/config/',
247
260
  parameter_list = 'parameter/',
248
261
  parameter_template_list = 'parameter/template/',
262
+ tag_list = 'tag/',
249
263
 
250
264
  // Internal system things
251
265
  system_internal_trace_end = 'system-internal/observability/end'
@@ -10,6 +10,7 @@ export interface ModelInformationInterface {
10
10
  url_detail?: string;
11
11
  api_endpoint: ApiEndpoints;
12
12
  admin_url?: string;
13
+ pk_field?: string;
13
14
  supports_barcode?: boolean;
14
15
  icon: keyof InvenTreeIconType;
15
16
  }
@@ -117,8 +118,8 @@ export const ModelInformationDict: ModelDict = {
117
118
  icon: 'history'
118
119
  },
119
120
  build: {
120
- label: () => t`Build`,
121
- label_multiple: () => t`Builds`,
121
+ label: () => t`Build Order`,
122
+ label_multiple: () => t`Build Orders`,
122
123
  url_overview: '/manufacturing/index/buildorders/',
123
124
  url_detail: '/manufacturing/build-order/:pk/',
124
125
  api_endpoint: ApiEndpoints.build_order_list,
@@ -207,6 +208,22 @@ export const ModelInformationDict: ModelDict = {
207
208
  api_endpoint: ApiEndpoints.return_order_line_list,
208
209
  icon: 'return_orders'
209
210
  },
211
+ transferorder: {
212
+ label: () => t`Transfer Order`,
213
+ label_multiple: () => t`Transfer Orders`,
214
+ url_overview: '/stock/location/index/transfer-orders',
215
+ url_detail: '/stock/transfer-order/:pk/',
216
+ api_endpoint: ApiEndpoints.transfer_order_list,
217
+ admin_url: '/order/transferorder/',
218
+ supports_barcode: true,
219
+ icon: 'transfer_orders'
220
+ },
221
+ transferorderlineitem: {
222
+ label: () => t`Transfer Order Line Item`,
223
+ label_multiple: () => t`Transfer Order Line Items`,
224
+ api_endpoint: ApiEndpoints.transfer_order_line_list,
225
+ icon: 'transfer-orders'
226
+ },
210
227
  address: {
211
228
  label: () => t`Address`,
212
229
  label_multiple: () => t`Addresses`,
@@ -302,5 +319,11 @@ export const ModelInformationDict: ModelDict = {
302
319
  url_overview: '/settings/admin/errors',
303
320
  url_detail: '/settings/admin/errors/:pk/',
304
321
  icon: 'exclamation'
322
+ },
323
+ tag: {
324
+ label: () => t`Tag`,
325
+ label_multiple: () => t`Tags`,
326
+ api_endpoint: ApiEndpoints.tag_list,
327
+ icon: 'tag'
305
328
  }
306
329
  };
@@ -24,6 +24,8 @@ export enum ModelType {
24
24
  salesordershipment = 'salesordershipment',
25
25
  returnorder = 'returnorder',
26
26
  returnorderlineitem = 'returnorderlineitem',
27
+ transferorder = 'transferorder',
28
+ transferorderlineitem = 'transferorderlineitem',
27
29
  importsession = 'importsession',
28
30
  address = 'address',
29
31
  contact = 'contact',
@@ -36,7 +38,8 @@ export enum ModelType {
36
38
  contenttype = 'contenttype',
37
39
  selectionlist = 'selectionlist',
38
40
  selectionentry = 'selectionentry',
39
- error = 'error'
41
+ error = 'error',
42
+ tag = 'tag'
40
43
  }
41
44
 
42
45
  export enum PluginPanelKey {
@@ -11,6 +11,7 @@ export enum UserRoles {
11
11
  part_category = 'part_category',
12
12
  purchase_order = 'purchase_order',
13
13
  return_order = 'return_order',
14
+ transfer_order = 'transfer_order',
14
15
  sales_order = 'sales_order',
15
16
  stock = 'stock',
16
17
  stock_location = 'stock_location'
@@ -40,6 +41,8 @@ export function userRoleLabel(role: UserRoles): string {
40
41
  return t`Purchase Orders`;
41
42
  case UserRoles.return_order:
42
43
  return t`Return Orders`;
44
+ case UserRoles.transfer_order:
45
+ return t`Transfer Orders`;
43
46
  case UserRoles.sales_order:
44
47
  return t`Sales Orders`;
45
48
  case UserRoles.stock:
@@ -55,7 +55,7 @@ export function getDetailUrl(
55
55
  }
56
56
  }
57
57
 
58
- console.error(`No detail URL found for model ${model} <${pk}>`);
58
+ console.warn(`No detail URL found for model ${model} <${pk}>`);
59
59
  return '';
60
60
  }
61
61
 
@@ -1,6 +1,10 @@
1
1
  import { useLocalStorage } from '@mantine/hooks';
2
2
  import { useCallback, useEffect, useMemo } from 'react';
3
- import type { FilterSetState, TableFilter } from '../types/Filters';
3
+ import type {
4
+ FilterSetState,
5
+ NamedFilterSet,
6
+ TableFilter
7
+ } from '../types/Filters';
4
8
 
5
9
  export default function useFilterSet(
6
10
  filterKey: string,
@@ -16,6 +20,16 @@ export default function useFilterSet(
16
20
  getInitialValueInEffect: false
17
21
  });
18
22
 
23
+ // Named filter set snapshots (saved to local storage, separate key)
24
+ const [storedNamedSets, setStoredNamedSets] = useLocalStorage<
25
+ NamedFilterSet[]
26
+ >({
27
+ key: `inventree-filtersets-${filterKey}`,
28
+ defaultValue: [],
29
+ sync: false,
30
+ getInitialValueInEffect: false
31
+ });
32
+
19
33
  useEffect(() => {
20
34
  if (storedFilters == null) {
21
35
  setStoredFilters(initialFilters || []);
@@ -26,7 +40,6 @@ export default function useFilterSet(
26
40
  return storedFilters ?? initialFilters ?? [];
27
41
  }, [storedFilters, initialFilters]);
28
42
 
29
- // Callback to clear all active filters from the table
30
43
  const clearActiveFilters = useCallback(() => {
31
44
  setStoredFilters([]);
32
45
  }, []);
@@ -38,10 +51,48 @@ export default function useFilterSet(
38
51
  [setStoredFilters]
39
52
  );
40
53
 
54
+ const saveFilterSet = useCallback(
55
+ (name: string) => {
56
+ const snapshot = activeFilters.map(
57
+ ({ name: n, value, displayValue }) => ({
58
+ name: n,
59
+ value,
60
+ displayValue
61
+ })
62
+ );
63
+ setStoredNamedSets((prev) => {
64
+ const without = (prev ?? []).filter((s) => s.name !== name);
65
+ return [...without, { name, filters: snapshot }];
66
+ });
67
+ },
68
+ [activeFilters, setStoredNamedSets]
69
+ );
70
+
71
+ const loadFilterSet = useCallback(
72
+ (name: string) => {
73
+ const saved = (storedNamedSets ?? []).find((s) => s.name === name);
74
+ if (saved) {
75
+ setStoredFilters(saved.filters as TableFilter[]);
76
+ }
77
+ },
78
+ [storedNamedSets, setStoredFilters]
79
+ );
80
+
81
+ const deleteFilterSet = useCallback(
82
+ (name: string) => {
83
+ setStoredNamedSets((prev) => (prev ?? []).filter((s) => s.name !== name));
84
+ },
85
+ [setStoredNamedSets]
86
+ );
87
+
41
88
  return {
42
89
  filterKey,
43
90
  activeFilters,
44
91
  setActiveFilters,
45
- clearActiveFilters
92
+ clearActiveFilters,
93
+ savedFilterSets: storedNamedSets ?? [],
94
+ saveFilterSet,
95
+ loadFilterSet,
96
+ deleteFilterSet
46
97
  };
47
98
  }
package/lib/index.ts CHANGED
@@ -110,6 +110,7 @@ export { ProgressBar } from './components/ProgressBar';
110
110
  export { PassFailButton, YesNoButton } from './components/YesNoButton';
111
111
  export { SearchInput } from './components/SearchInput';
112
112
  export { TableColumnSelect } from './components/TableColumnSelect';
113
+ export { default as TagsList } from './components/TagsList';
113
114
  export { default as InvenTreeTable } from './components/InvenTreeTable';
114
115
  export {
115
116
  RowViewAction,
@@ -152,3 +153,7 @@ export {
152
153
  useStoredTableState
153
154
  } from './states/StoredTableState';
154
155
  export { useLocalLibState } from './states/LocalLibState';
156
+
157
+ // Plugin development utilities and hooks
158
+ export { default as InventreeHmrPlugin } from './plugin/InventreeHmrPlugin';
159
+ export { default as LocalizedComponent } from './plugin/LocalizedComponent';
@@ -0,0 +1,40 @@
1
+ import type { Plugin } from 'vite';
2
+
3
+ /**
4
+ * Vite plugin which enables hot module replacement (HMR) for InvenTree plugin development.
5
+ *
6
+ * This is for use with the InvenTree plugin creator tool,
7
+ * allowing frontend plugin code to be "live reloaded" during development.
8
+ */
9
+ export default function InventreeHmrPlugin(): Plugin {
10
+ const fileRegex = /\.(js|jsx|ts|tsx)(\?|$)/;
11
+
12
+ const hmrBlock = [
13
+ '',
14
+ '// __inventree_hmr_injected__',
15
+ 'if (import.meta.hot) {',
16
+ ' import.meta.hot.accept((newModule) => {',
17
+ ' const key = new URL(import.meta.url).origin + new URL(import.meta.url).pathname;',
18
+ ' window.__plugin_hmr_callbacks?.[key]?.forEach(callback => {',
19
+ ' callback(newModule);',
20
+ ' });',
21
+ ' })',
22
+ '}'
23
+ ];
24
+
25
+ return {
26
+ name: 'inventree-hmr-plugin',
27
+ enforce: 'post',
28
+
29
+ transform(code, id) {
30
+ if (!fileRegex.test(id)) return;
31
+ if (id.includes('node_modules')) return;
32
+ if (code.includes('__inventree_hmr_injected__')) return;
33
+
34
+ return {
35
+ code: code + hmrBlock.join('\n'),
36
+ map: null
37
+ };
38
+ }
39
+ };
40
+ }
@@ -0,0 +1,87 @@
1
+ import { I18n } from '@lingui/core';
2
+ import { I18nProvider } from '@lingui/react';
3
+ import { Skeleton } from '@mantine/core';
4
+ import { useEffect, useState } from 'react';
5
+
6
+ /**
7
+ * Attempt to load the locale file for the given locale, returning null if it fails
8
+ */
9
+ async function tryLoadLocale(locale: string): Promise<any> {
10
+ try {
11
+ const messages = await import(`./locales/${locale}/messages.ts`);
12
+ return messages;
13
+ } catch (error) {
14
+ console.warn(`Failed to load locale ${locale}`);
15
+ return null;
16
+ }
17
+ }
18
+
19
+ /**
20
+ * Helper function to dynamically load frontend translations,
21
+ * based on the provided locale.
22
+ */
23
+ async function loadPluginLocale(i18n: I18n, locale: string) {
24
+ let messages = null;
25
+
26
+ // Find the most specific locale file possible, with fallbacks to less specific locales if necessary
27
+ messages = await tryLoadLocale(locale);
28
+
29
+ if (!messages && locale.includes('-')) {
30
+ const fallbackLocale = locale.split('-')[0];
31
+ console.debug(
32
+ `Locale ${locale} not found, trying fallback locale ${fallbackLocale}`
33
+ );
34
+ messages = await tryLoadLocale(fallbackLocale);
35
+ }
36
+
37
+ if (!messages && locale.includes('_')) {
38
+ const fallbackLocale = locale.split('_')[0];
39
+ console.debug(
40
+ `Locale ${locale} not found, trying fallback locale ${fallbackLocale}`
41
+ );
42
+ messages = await tryLoadLocale(fallbackLocale);
43
+ }
44
+
45
+ if (!messages && locale !== 'en') {
46
+ console.debug(`Locale ${locale} not found, trying fallback locale en`);
47
+ messages = await tryLoadLocale('en');
48
+ }
49
+
50
+ if (messages?.messages) {
51
+ i18n.load(locale, messages.messages);
52
+ i18n.activate(locale);
53
+ } else {
54
+ console.error(`Failed to load any locale for ${locale}`);
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Wrapper function for a plugin-defined component which needs to support dynamic locale loading.
60
+ *
61
+ * This is primarily designed for usage by the InvenTree plugin creator tool
62
+ */
63
+ export default function LocalizedComponent({
64
+ i18n,
65
+ locale,
66
+ children
67
+ }: {
68
+ i18n: I18n;
69
+ locale: string;
70
+ children: React.ReactNode;
71
+ }) {
72
+ const [loaded, setLoaded] = useState(false);
73
+
74
+ // Reload the component when the locale changes
75
+ useEffect(() => {
76
+ setLoaded(false);
77
+ loadPluginLocale(i18n, locale).then(() => {
78
+ setLoaded(true);
79
+ });
80
+ }, [i18n, locale]);
81
+
82
+ return loaded ? (
83
+ <I18nProvider i18n={i18n}>{children}</I18nProvider>
84
+ ) : (
85
+ <Skeleton w='100%' animate />
86
+ );
87
+ }
@@ -41,6 +41,7 @@ export type TableFilter = {
41
41
  name: string;
42
42
  label?: string;
43
43
  description?: string;
44
+ placeholder?: string;
44
45
  type?: TableFilterType;
45
46
  choices?: TableFilterChoice[];
46
47
  choiceFunction?: () => TableFilterChoice[];
@@ -52,6 +53,16 @@ export type TableFilter = {
52
53
  apiFilter?: Record<string, any>;
53
54
  model?: ModelType;
54
55
  modelRenderer?: (instance: any) => string;
56
+ transform?: (item: any) => TableFilterChoice;
57
+ multi?: boolean;
58
+ };
59
+
60
+ /*
61
+ * A named snapshot of a set of active filters, saved to local storage.
62
+ */
63
+ export type NamedFilterSet = {
64
+ name: string;
65
+ filters: Pick<TableFilter, 'name' | 'value' | 'displayValue'>[];
55
66
  };
56
67
 
57
68
  /*
@@ -62,10 +73,18 @@ export type TableFilter = {
62
73
  * activeFilters: An array of active filters
63
74
  * setActiveFilters: A function to set the active filters
64
75
  * clearActiveFilters: A function to clear all active filters
76
+ * savedFilterSets: Named filter set snapshots persisted to local storage
77
+ * saveFilterSet: Save the current active filters under a given name
78
+ * loadFilterSet: Replace active filters with a previously saved named set
79
+ * deleteFilterSet: Remove a saved named filter set by name
65
80
  */
66
81
  export type FilterSetState = {
67
82
  filterKey: string;
68
83
  activeFilters: TableFilter[];
69
84
  setActiveFilters: (filters: TableFilter[]) => void;
70
85
  clearActiveFilters: () => void;
86
+ savedFilterSets: NamedFilterSet[];
87
+ saveFilterSet: (name: string) => void;
88
+ loadFilterSet: (name: string) => void;
89
+ deleteFilterSet: (name: string) => void;
71
90
  };
@@ -2,6 +2,7 @@ import type { DefaultMantineColor, MantineStyleProp } from '@mantine/core';
2
2
  import type { UseFormReturnType } from '@mantine/form';
3
3
  import type { JSX, ReactNode } from 'react';
4
4
  import type { FieldValues, UseFormReturn } from 'react-hook-form';
5
+ import type { NavigateFunction } from 'react-router-dom';
5
6
  import type { ApiEndpoints } from '../enums/ApiEndpoints';
6
7
  import type { ModelType } from '../enums/ModelType';
7
8
  import type { PathParams, UiSizeType } from './Core';
@@ -98,7 +99,8 @@ export type ApiFormFieldType = {
98
99
  | 'file upload'
99
100
  | 'nested object'
100
101
  | 'dependent field'
101
- | 'table';
102
+ | 'table'
103
+ | 'tags';
102
104
  api_url?: string;
103
105
  pk_field?: string;
104
106
  model?: ModelType;
@@ -160,6 +162,7 @@ export type ApiFormFieldSet = Record<string, ApiFormFieldType>;
160
162
  * @param modelType : Define a model type for this form
161
163
  * @param follow : Boolean, follow the result of the form (if possible)
162
164
  * @param table : Table to update on success (if provided)
165
+ * @param navigate : Optional navigate function to use for following results (if follow is true)
163
166
  */
164
167
  export interface ApiFormProps {
165
168
  url: ApiEndpoints | string;
@@ -189,6 +192,7 @@ export interface ApiFormProps {
189
192
  follow?: boolean;
190
193
  actions?: ApiFormAction[];
191
194
  timeout?: number;
195
+ navigate?: NavigateFunction;
192
196
  keepOpenOption?: boolean;
193
197
  onKeepOpenChange?: (keepOpen: boolean) => void;
194
198
  }
@@ -15,6 +15,7 @@ export interface InstanceRenderInterface {
15
15
  link?: boolean;
16
16
  navigate?: any;
17
17
  showSecondary?: boolean;
18
+ showHover?: boolean;
18
19
  extra?: Record<string, any>;
19
20
  }
20
21
 
@@ -84,7 +84,7 @@ export type TableState = {
84
84
  * @param editable - Whether the value of this column can be edited
85
85
  * @param definition - Optional field definition for the column
86
86
  * @param render - A custom render function
87
- * @param filter - A custom filter function
87
+ * @param filter - Filter name (string) to look up from tableFilters and attach an inline icon, or a custom render function for the filter popover
88
88
  * @param filtering - Whether the column is filterable
89
89
  * @param width - The width of the column
90
90
  * @param minWidth - The minimum width of the column
@@ -109,7 +109,10 @@ export type TableColumnProps<T = any> = {
109
109
  editable?: boolean;
110
110
  definition?: ApiFormFieldType;
111
111
  render?: (record: T, index?: number) => any;
112
- filter?: any;
112
+ filter?:
113
+ | string
114
+ | string[]
115
+ | (({ close }: { close: () => void }) => ReactNode);
113
116
  filtering?: boolean;
114
117
  width?: number;
115
118
  minWidth?: string | number;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@inventreedb/ui",
3
3
  "description": "UI components for the InvenTree project",
4
- "version": "1.4.0",
4
+ "version": "1.4.2",
5
5
  "private": false,
6
6
  "type": "module",
7
7
  "license": "MIT",
@@ -115,7 +115,7 @@
115
115
  "@lingui/babel-plugin-lingui-macro": "^5.9.2",
116
116
  "@lingui/cli": "^5.9.2",
117
117
  "@lingui/macro": "^5.9.2",
118
- "@playwright/test": "^1.58.2",
118
+ "@playwright/test": "^1.16.0",
119
119
  "@types/node": "^25.5.0",
120
120
  "@types/qrcode": "^1.5.5",
121
121
  "@types/react": "^19.2.14",