@inventreedb/ui 1.4.0 → 1.4.1
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.
- package/CHANGELOG.md +14 -0
- package/dist/.vite/manifest.json +40 -1
- package/dist/_virtual/dynamic-import-helper.js +20 -0
- package/dist/_virtual/dynamic-import-helper.js.map +1 -0
- package/dist/components/Boundary.js +5 -4
- package/dist/components/Boundary.js.map +1 -1
- package/dist/components/TableColumnSelect.js +1 -1
- package/dist/components/TableColumnSelect.js.map +1 -1
- package/dist/components/TagsList.d.ts +3 -0
- package/dist/components/TagsList.js +21 -0
- package/dist/components/TagsList.js.map +1 -0
- package/dist/enums/ApiEndpoints.d.ts +15 -4
- package/dist/enums/ApiEndpoints.js +15 -4
- package/dist/enums/ApiEndpoints.js.map +1 -1
- package/dist/enums/ModelInformation.d.ts +1 -0
- package/dist/enums/ModelInformation.js +54 -2
- package/dist/enums/ModelInformation.js.map +1 -1
- package/dist/enums/ModelType.d.ts +4 -1
- package/dist/enums/ModelType.js +3 -0
- package/dist/enums/ModelType.js.map +1 -1
- package/dist/enums/Roles.d.ts +1 -0
- package/dist/enums/Roles.js +1 -0
- package/dist/enums/Roles.js.map +1 -1
- package/dist/functions/Navigation.js +1 -1
- package/dist/functions/Navigation.js.map +1 -1
- package/dist/hooks/UseFilterSet.js +38 -1
- package/dist/hooks/UseFilterSet.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.js +16 -10
- package/dist/index.js.map +1 -1
- package/dist/node_modules/@tabler/icons-react/dist/esm/icons/IconTag.js +14 -0
- package/dist/node_modules/@tabler/icons-react/dist/esm/icons/IconTag.js.map +1 -0
- package/dist/plugin/InventreeHmrPlugin.d.ts +8 -0
- package/dist/plugin/InventreeHmrPlugin.js +21 -0
- package/dist/plugin/InventreeHmrPlugin.js.map +1 -0
- package/dist/plugin/LocalizedComponent.d.ts +9 -0
- package/dist/plugin/LocalizedComponent.js +57 -0
- package/dist/plugin/LocalizedComponent.js.map +1 -0
- package/dist/types/Filters.d.ts +11 -0
- package/dist/types/Forms.d.ts +4 -1
- package/dist/types/Plugins.js +1 -1
- package/dist/types/Rendering.d.ts +1 -0
- package/dist/types/Tables.d.ts +4 -2
- package/lib/components/Boundary.tsx +7 -3
- package/lib/components/TableColumnSelect.tsx +1 -1
- package/lib/components/TagsList.tsx +27 -0
- package/lib/enums/ApiEndpoints.tsx +18 -4
- package/lib/enums/ModelInformation.tsx +25 -2
- package/lib/enums/ModelType.tsx +4 -1
- package/lib/enums/Roles.tsx +3 -0
- package/lib/functions/Navigation.tsx +1 -1
- package/lib/hooks/UseFilterSet.tsx +54 -3
- package/lib/index.ts +5 -0
- package/lib/plugin/InventreeHmrPlugin.tsx +40 -0
- package/lib/plugin/LocalizedComponent.tsx +85 -0
- package/lib/types/Filters.tsx +19 -0
- package/lib/types/Forms.tsx +5 -1
- package/lib/types/Rendering.tsx +1 -0
- package/lib/types/Tables.tsx +5 -2
- package/package.json +2 -2
package/dist/types/Tables.d.ts
CHANGED
|
@@ -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 -
|
|
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?:
|
|
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
|
-
|
|
18
|
-
|
|
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`
|
|
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
|
};
|
package/lib/enums/ModelType.tsx
CHANGED
|
@@ -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 {
|
package/lib/enums/Roles.tsx
CHANGED
|
@@ -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:
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { useLocalStorage } from '@mantine/hooks';
|
|
2
2
|
import { useCallback, useEffect, useMemo } from 'react';
|
|
3
|
-
import type {
|
|
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,85 @@
|
|
|
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(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
|
+
locale,
|
|
65
|
+
children
|
|
66
|
+
}: {
|
|
67
|
+
locale: string;
|
|
68
|
+
children: React.ReactNode;
|
|
69
|
+
}) {
|
|
70
|
+
const [loaded, setLoaded] = useState(false);
|
|
71
|
+
|
|
72
|
+
// Reload the component when the locale changes
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
setLoaded(false);
|
|
75
|
+
loadPluginLocale(locale).then(() => {
|
|
76
|
+
setLoaded(true);
|
|
77
|
+
});
|
|
78
|
+
}, [locale]);
|
|
79
|
+
|
|
80
|
+
return loaded ? (
|
|
81
|
+
<I18nProvider i18n={i18n}>{children}</I18nProvider>
|
|
82
|
+
) : (
|
|
83
|
+
<Skeleton w='100%' animate />
|
|
84
|
+
);
|
|
85
|
+
}
|
package/lib/types/Filters.tsx
CHANGED
|
@@ -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
|
};
|
package/lib/types/Forms.tsx
CHANGED
|
@@ -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
|
}
|
package/lib/types/Rendering.tsx
CHANGED
package/lib/types/Tables.tsx
CHANGED
|
@@ -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 -
|
|
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?:
|
|
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.
|
|
4
|
+
"version": "1.4.1",
|
|
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.
|
|
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",
|