mirror of
https://github.com/inventree/InvenTree.git
synced 2025-06-16 03:55:41 +00:00
[Plugin] Plugin context (#9439)
* Pass more stuff to window * Expose form functions to plugin context * Breaking: Render plugin component in context tree - Required due to createRoot function - Adds necessary context providers * Fix context * Provide MantineThemeContext * Bundle mantine/core * Hack for useNavigate within ApiForm - Errors out if called within plugin context - Workaround to catch the error * Update build cmd * Define config for building "Library" mode * Update package.json * Add basic index file * Factor out ApiEndpoints * factor out ModelType * Factor out role enums * Further refactoring * More refactoring * Cleanup * Expose apiUrl function * Add instance data to plugin context type def * Tweaks for loading plugin components - LanguageContext must be on the inside * Tweak StylishText * Externalize notifications system * Update lingui config * Add functions for checking plugin interface version * Extract package version at build time * Enhance version checking * Revert variable name change * Public package * Add README.md * adjust packge name * Adjust name to include org * Update project files * Add basic changelog info * Refactoring to expose URL functions * Refactor navigation functions * Update package and README * Improve navigateToLink function * Refactor stylish text - Move into ./lib - Do not require user state * Revert changes - StylishText throws error in plugin - Low priority, can work out later * expose function to refresh page index * Provide RemoteComponent with a method to reload itself * Bump version * Cleanup tests * Prevent duplicate --emptyOutDir arg * Tweak playwright tests * Expose role and permission enums * Fix imports * Updated docs * Fix spelling, typos, etc * Include more package version information * Expose more version context * Cleanup * Probably don't need hooks * Fix links * Docs updates * Fix links
This commit is contained in:
@ -41,8 +41,8 @@
|
||||
"pseudo-LOCALE"],
|
||||
"catalogs": [{
|
||||
"path": "src/locales/{locale}/messages",
|
||||
"include": ["src"],
|
||||
"exclude": ["**/node_modules/**"]
|
||||
"include": ["src", "lib"],
|
||||
"exclude": ["**/node_modules/**", "./dist/**"]
|
||||
}],
|
||||
"format": "po",
|
||||
"orderBy": "origin",
|
||||
|
14
src/frontend/.npmignore
Normal file
14
src/frontend/.npmignore
Normal file
@ -0,0 +1,14 @@
|
||||
# Testing code
|
||||
playwright.config.ts
|
||||
playwright/
|
||||
tests/
|
||||
test-results/
|
||||
|
||||
# Source files (not part of public API)
|
||||
src/
|
||||
|
||||
# Build output
|
||||
node_modules/
|
||||
|
||||
# Other files
|
||||
.gitignore
|
15
src/frontend/CHANGELOG.md
Normal file
15
src/frontend/CHANGELOG.md
Normal file
@ -0,0 +1,15 @@
|
||||
## InvenTree UI Components - Changelog
|
||||
|
||||
This file contains historical changelog information for the InvenTree UI components library.
|
||||
|
||||
### 1.0.0 - April 2025
|
||||
|
||||
Published the first version of the UI components API. This allows external plugins to hook into the InvenTree user interface, and provides global access to the following objects:
|
||||
|
||||
- `window.React`: The core `react` library running on the UI
|
||||
- `window.ReactDOM`: The `react-dom` library
|
||||
- `window.ReactDOMClient`: The `react-dom/client` library
|
||||
- `window.MantineCore`: The `@mantine/core` library
|
||||
- `window.MantineNotifications`: The `@mantine/notifications` library
|
||||
|
||||
All of these components can be "externalized" in the plugin build step.
|
21
src/frontend/LICENSE
Normal file
21
src/frontend/LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017 - InvenTree Developers
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
28
src/frontend/README.md
Normal file
28
src/frontend/README.md
Normal file
@ -0,0 +1,28 @@
|
||||
## inventree-ui
|
||||
|
||||
User Interface (UI) elements for the [InvenTree](https://inventree.org) web interface.
|
||||
|
||||
|
||||
### Description
|
||||
|
||||
This package provides a public interface allowing plugins to hook into core UI functionality. In particular, it defines a set of interface types provided by the InvenTree user interface, to be used by a custom plugin to implement some custom UI feature.
|
||||
|
||||
This library is intended to be used for creating plugins - any other use is outside of its scope and is not supported.
|
||||
|
||||
### Plugin Creator
|
||||
|
||||
This library is intended to be used with the [InvenTree Plugin Creator](https://github.com/inventree/plugin-creator). Read the documentation for the plugin creation tool for more information.
|
||||
|
||||
The plugin creation tool uses the types provided in this package at build time, but it is intended that most of the major packages are *externalized* - as these are provided as global objects by the core InvenTree UI code.
|
||||
|
||||
### Installation
|
||||
|
||||
This should be installed as a part of the plugin creator tool. If you need to install it manually, e.g. using `npm`:
|
||||
|
||||
```
|
||||
npm i @inventreedb/ui
|
||||
```
|
||||
|
||||
### Versioning
|
||||
|
||||
Each change to the plugin API will be described in the [CHANGELOG file](./CHANGELOG.md).
|
279
src/frontend/lib/enums/ModelInformation.tsx
Normal file
279
src/frontend/lib/enums/ModelInformation.tsx
Normal file
@ -0,0 +1,279 @@
|
||||
import { t } from '@lingui/core/macro';
|
||||
import type { InvenTreeIconType } from '../types/Icons';
|
||||
import { ApiEndpoints } from './ApiEndpoints';
|
||||
import type { ModelType } from './ModelType';
|
||||
|
||||
export interface ModelInformationInterface {
|
||||
label: string;
|
||||
label_multiple: string;
|
||||
url_overview?: string;
|
||||
url_detail?: string;
|
||||
api_endpoint: ApiEndpoints;
|
||||
admin_url?: string;
|
||||
icon: keyof InvenTreeIconType;
|
||||
}
|
||||
|
||||
export interface TranslatableModelInformationInterface
|
||||
extends Omit<ModelInformationInterface, 'label' | 'label_multiple'> {
|
||||
label: () => string;
|
||||
label_multiple: () => string;
|
||||
}
|
||||
|
||||
export type ModelDict = {
|
||||
[key in keyof typeof ModelType]: TranslatableModelInformationInterface;
|
||||
};
|
||||
|
||||
export const ModelInformationDict: ModelDict = {
|
||||
part: {
|
||||
label: () => t`Part`,
|
||||
label_multiple: () => t`Parts`,
|
||||
url_overview: '/part/category/index/parts',
|
||||
url_detail: '/part/:pk/',
|
||||
api_endpoint: ApiEndpoints.part_list,
|
||||
admin_url: '/part/part/',
|
||||
icon: 'part'
|
||||
},
|
||||
partparametertemplate: {
|
||||
label: () => t`Part Parameter Template`,
|
||||
label_multiple: () => t`Part Parameter Templates`,
|
||||
url_detail: '/partparametertemplate/:pk/',
|
||||
api_endpoint: ApiEndpoints.part_parameter_template_list,
|
||||
icon: 'test_templates'
|
||||
},
|
||||
parttesttemplate: {
|
||||
label: () => t`Part Test Template`,
|
||||
label_multiple: () => t`Part Test Templates`,
|
||||
url_detail: '/parttesttemplate/:pk/',
|
||||
api_endpoint: ApiEndpoints.part_test_template_list,
|
||||
icon: 'test'
|
||||
},
|
||||
supplierpart: {
|
||||
label: () => t`Supplier Part`,
|
||||
label_multiple: () => t`Supplier Parts`,
|
||||
url_overview: '/purchasing/index/supplier-parts',
|
||||
url_detail: '/purchasing/supplier-part/:pk/',
|
||||
api_endpoint: ApiEndpoints.supplier_part_list,
|
||||
admin_url: '/company/supplierpart/',
|
||||
icon: 'supplier_part'
|
||||
},
|
||||
manufacturerpart: {
|
||||
label: () => t`Manufacturer Part`,
|
||||
label_multiple: () => t`Manufacturer Parts`,
|
||||
url_overview: '/purchasing/index/manufacturer-parts',
|
||||
url_detail: '/purchasing/manufacturer-part/:pk/',
|
||||
api_endpoint: ApiEndpoints.manufacturer_part_list,
|
||||
admin_url: '/company/manufacturerpart/',
|
||||
icon: 'manufacturers'
|
||||
},
|
||||
partcategory: {
|
||||
label: () => t`Part Category`,
|
||||
label_multiple: () => t`Part Categories`,
|
||||
url_overview: '/part/category/parts/subcategories',
|
||||
url_detail: '/part/category/:pk/',
|
||||
api_endpoint: ApiEndpoints.category_list,
|
||||
admin_url: '/part/partcategory/',
|
||||
icon: 'category'
|
||||
},
|
||||
stockitem: {
|
||||
label: () => t`Stock Item`,
|
||||
label_multiple: () => t`Stock Items`,
|
||||
url_overview: '/stock/location/index/stock-items',
|
||||
url_detail: '/stock/item/:pk/',
|
||||
api_endpoint: ApiEndpoints.stock_item_list,
|
||||
admin_url: '/stock/stockitem/',
|
||||
icon: 'stock'
|
||||
},
|
||||
stocklocation: {
|
||||
label: () => t`Stock Location`,
|
||||
label_multiple: () => t`Stock Locations`,
|
||||
url_overview: '/stock/location',
|
||||
url_detail: '/stock/location/:pk/',
|
||||
api_endpoint: ApiEndpoints.stock_location_list,
|
||||
admin_url: '/stock/stocklocation/',
|
||||
icon: 'location'
|
||||
},
|
||||
stocklocationtype: {
|
||||
label: () => t`Stock Location Type`,
|
||||
label_multiple: () => t`Stock Location Types`,
|
||||
api_endpoint: ApiEndpoints.stock_location_type_list,
|
||||
icon: 'location'
|
||||
},
|
||||
stockhistory: {
|
||||
label: () => t`Stock History`,
|
||||
label_multiple: () => t`Stock Histories`,
|
||||
api_endpoint: ApiEndpoints.stock_tracking_list,
|
||||
icon: 'history'
|
||||
},
|
||||
build: {
|
||||
label: () => t`Build`,
|
||||
label_multiple: () => t`Builds`,
|
||||
url_overview: '/manufacturing/index/buildorders/',
|
||||
url_detail: '/manufacturing/build-order/:pk/',
|
||||
api_endpoint: ApiEndpoints.build_order_list,
|
||||
admin_url: '/build/build/',
|
||||
icon: 'build_order'
|
||||
},
|
||||
buildline: {
|
||||
label: () => t`Build Line`,
|
||||
label_multiple: () => t`Build Lines`,
|
||||
url_overview: '/build/line',
|
||||
url_detail: '/build/line/:pk/',
|
||||
api_endpoint: ApiEndpoints.build_line_list,
|
||||
icon: 'build_order'
|
||||
},
|
||||
builditem: {
|
||||
label: () => t`Build Item`,
|
||||
label_multiple: () => t`Build Items`,
|
||||
api_endpoint: ApiEndpoints.build_item_list,
|
||||
icon: 'build_order'
|
||||
},
|
||||
company: {
|
||||
label: () => t`Company`,
|
||||
label_multiple: () => t`Companies`,
|
||||
url_detail: '/company/:pk/',
|
||||
api_endpoint: ApiEndpoints.company_list,
|
||||
admin_url: '/company/company/',
|
||||
icon: 'building'
|
||||
},
|
||||
projectcode: {
|
||||
label: () => t`Project Code`,
|
||||
label_multiple: () => t`Project Codes`,
|
||||
url_detail: '/project-code/:pk/',
|
||||
api_endpoint: ApiEndpoints.project_code_list,
|
||||
icon: 'list_details'
|
||||
},
|
||||
purchaseorder: {
|
||||
label: () => t`Purchase Order`,
|
||||
label_multiple: () => t`Purchase Orders`,
|
||||
url_overview: '/purchasing/index/purchaseorders',
|
||||
url_detail: '/purchasing/purchase-order/:pk/',
|
||||
api_endpoint: ApiEndpoints.purchase_order_list,
|
||||
admin_url: '/order/purchaseorder/',
|
||||
icon: 'purchase_orders'
|
||||
},
|
||||
purchaseorderlineitem: {
|
||||
label: () => t`Purchase Order Line`,
|
||||
label_multiple: () => t`Purchase Order Lines`,
|
||||
api_endpoint: ApiEndpoints.purchase_order_line_list,
|
||||
icon: 'purchase_orders'
|
||||
},
|
||||
salesorder: {
|
||||
label: () => t`Sales Order`,
|
||||
label_multiple: () => t`Sales Orders`,
|
||||
url_overview: '/sales/index/salesorders',
|
||||
url_detail: '/sales/sales-order/:pk/',
|
||||
api_endpoint: ApiEndpoints.sales_order_list,
|
||||
admin_url: '/order/salesorder/',
|
||||
icon: 'sales_orders'
|
||||
},
|
||||
salesordershipment: {
|
||||
label: () => t`Sales Order Shipment`,
|
||||
label_multiple: () => t`Sales Order Shipments`,
|
||||
url_detail: '/sales/shipment/:pk/',
|
||||
api_endpoint: ApiEndpoints.sales_order_shipment_list,
|
||||
icon: 'sales_orders'
|
||||
},
|
||||
returnorder: {
|
||||
label: () => t`Return Order`,
|
||||
label_multiple: () => t`Return Orders`,
|
||||
url_overview: '/sales/index/returnorders',
|
||||
url_detail: '/sales/return-order/:pk/',
|
||||
api_endpoint: ApiEndpoints.return_order_list,
|
||||
admin_url: '/order/returnorder/',
|
||||
icon: 'return_orders'
|
||||
},
|
||||
returnorderlineitem: {
|
||||
label: () => t`Return Order Line Item`,
|
||||
label_multiple: () => t`Return Order Line Items`,
|
||||
api_endpoint: ApiEndpoints.return_order_line_list,
|
||||
icon: 'return_orders'
|
||||
},
|
||||
address: {
|
||||
label: () => t`Address`,
|
||||
label_multiple: () => t`Addresses`,
|
||||
url_detail: '/address/:pk/',
|
||||
api_endpoint: ApiEndpoints.address_list,
|
||||
icon: 'address'
|
||||
},
|
||||
contact: {
|
||||
label: () => t`Contact`,
|
||||
label_multiple: () => t`Contacts`,
|
||||
url_detail: '/contact/:pk/',
|
||||
api_endpoint: ApiEndpoints.contact_list,
|
||||
icon: 'group'
|
||||
},
|
||||
owner: {
|
||||
label: () => t`Owner`,
|
||||
label_multiple: () => t`Owners`,
|
||||
url_detail: '/owner/:pk/',
|
||||
api_endpoint: ApiEndpoints.owner_list,
|
||||
icon: 'group'
|
||||
},
|
||||
user: {
|
||||
label: () => t`User`,
|
||||
label_multiple: () => t`Users`,
|
||||
url_detail: '/core/user/:pk/',
|
||||
api_endpoint: ApiEndpoints.user_list,
|
||||
icon: 'user'
|
||||
},
|
||||
group: {
|
||||
label: () => t`Group`,
|
||||
label_multiple: () => t`Groups`,
|
||||
url_detail: '/core/group/:pk/',
|
||||
api_endpoint: ApiEndpoints.group_list,
|
||||
admin_url: '/auth/group/',
|
||||
icon: 'group'
|
||||
},
|
||||
importsession: {
|
||||
label: () => t`Import Session`,
|
||||
label_multiple: () => t`Import Sessions`,
|
||||
url_overview: '/settings/admin/import',
|
||||
url_detail: '/import/:pk/',
|
||||
api_endpoint: ApiEndpoints.import_session_list,
|
||||
icon: 'import'
|
||||
},
|
||||
labeltemplate: {
|
||||
label: () => t`Label Template`,
|
||||
label_multiple: () => t`Label Templates`,
|
||||
url_overview: '/settings/admin/labels',
|
||||
url_detail: '/settings/admin/labels/:pk/',
|
||||
api_endpoint: ApiEndpoints.label_list,
|
||||
icon: 'labels'
|
||||
},
|
||||
reporttemplate: {
|
||||
label: () => t`Report Template`,
|
||||
label_multiple: () => t`Report Templates`,
|
||||
url_overview: '/settings/admin/reports',
|
||||
url_detail: '/settings/admin/reports/:pk/',
|
||||
api_endpoint: ApiEndpoints.report_list,
|
||||
icon: 'reports'
|
||||
},
|
||||
pluginconfig: {
|
||||
label: () => t`Plugin Configuration`,
|
||||
label_multiple: () => t`Plugin Configurations`,
|
||||
url_overview: '/settings/admin/plugin',
|
||||
url_detail: '/settings/admin/plugin/:pk/',
|
||||
api_endpoint: ApiEndpoints.plugin_list,
|
||||
icon: 'plugin'
|
||||
},
|
||||
contenttype: {
|
||||
label: () => t`Content Type`,
|
||||
label_multiple: () => t`Content Types`,
|
||||
api_endpoint: ApiEndpoints.content_type_list,
|
||||
icon: 'list_details'
|
||||
},
|
||||
selectionlist: {
|
||||
label: () => t`Selection List`,
|
||||
label_multiple: () => t`Selection Lists`,
|
||||
api_endpoint: ApiEndpoints.selectionlist_list,
|
||||
icon: 'list_details'
|
||||
},
|
||||
error: {
|
||||
label: () => t`Error`,
|
||||
label_multiple: () => t`Errors`,
|
||||
api_endpoint: ApiEndpoints.error_report_list,
|
||||
url_overview: '/settings/admin/errors',
|
||||
url_detail: '/settings/admin/errors/:pk/',
|
||||
icon: 'exclamation'
|
||||
}
|
||||
};
|
42
src/frontend/lib/functions/Api.tsx
Normal file
42
src/frontend/lib/functions/Api.tsx
Normal file
@ -0,0 +1,42 @@
|
||||
import type { ApiEndpoints } from '../enums/ApiEndpoints';
|
||||
import type { PathParams } from '../types/Core';
|
||||
|
||||
/**
|
||||
* Function to return the API prefix.
|
||||
* For now it is fixed, but may be configurable in the future.
|
||||
*/
|
||||
export function apiPrefix(): string {
|
||||
return '/api/';
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an API URL with an endpoint and (optional) pk value
|
||||
*/
|
||||
export function apiUrl(
|
||||
endpoint: ApiEndpoints | string,
|
||||
pk?: any,
|
||||
pathParams?: PathParams
|
||||
): string {
|
||||
let _url = endpoint;
|
||||
|
||||
// If the URL does not start with a '/', add the API prefix
|
||||
if (!_url.startsWith('/')) {
|
||||
_url = apiPrefix() + _url;
|
||||
}
|
||||
|
||||
if (_url && pk) {
|
||||
if (_url.indexOf(':id') >= 0) {
|
||||
_url = _url.replace(':id', `${pk}`);
|
||||
} else {
|
||||
_url += `${pk}/`;
|
||||
}
|
||||
}
|
||||
|
||||
if (_url && pathParams) {
|
||||
for (const key in pathParams) {
|
||||
_url = _url.replace(`:${key}`, `${pathParams[key]}`);
|
||||
}
|
||||
}
|
||||
|
||||
return _url;
|
||||
}
|
70
src/frontend/lib/functions/Navigation.tsx
Normal file
70
src/frontend/lib/functions/Navigation.tsx
Normal file
@ -0,0 +1,70 @@
|
||||
import type { NavigateFunction } from 'react-router-dom';
|
||||
import { ModelInformationDict } from '../enums/ModelInformation';
|
||||
import type { ModelType } from '../enums/ModelType';
|
||||
import { cancelEvent } from './Events';
|
||||
|
||||
export const getBaseUrl = (): string =>
|
||||
(window as any).INVENTREE_SETTINGS?.base_url || 'web';
|
||||
|
||||
/**
|
||||
* Returns the detail view URL for a given model type
|
||||
*/
|
||||
export function getDetailUrl(
|
||||
model: ModelType,
|
||||
pk: number | string,
|
||||
absolute?: boolean
|
||||
): string {
|
||||
const modelInfo = ModelInformationDict[model];
|
||||
|
||||
if (pk === undefined || pk === null) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (!!pk && modelInfo && modelInfo.url_detail) {
|
||||
const url = modelInfo.url_detail.replace(':pk', pk.toString());
|
||||
const base = getBaseUrl();
|
||||
|
||||
if (absolute && base) {
|
||||
return `/${base}${url}`;
|
||||
} else {
|
||||
return url;
|
||||
}
|
||||
}
|
||||
|
||||
console.error(`No detail URL found for model ${model} <${pk}>`);
|
||||
return '';
|
||||
}
|
||||
|
||||
/*
|
||||
* Navigate to a provided link.
|
||||
* - If the link is to be opened externally, open it in a new tab.
|
||||
* - Otherwise, navigate using the provided navigate function.
|
||||
*/
|
||||
export const navigateToLink = (
|
||||
link: string,
|
||||
navigate: NavigateFunction,
|
||||
event: any
|
||||
) => {
|
||||
cancelEvent(event);
|
||||
|
||||
const base = `/${getBaseUrl()}`;
|
||||
|
||||
if (event?.ctrlKey || event?.shiftKey) {
|
||||
// Open the link in a new tab
|
||||
let url = link;
|
||||
if (link.startsWith('/') && !link.startsWith(base)) {
|
||||
url = `${base}${link}`;
|
||||
}
|
||||
window.open(url, '_blank');
|
||||
} else {
|
||||
// Navigate internally
|
||||
let url = link;
|
||||
|
||||
if (link.startsWith(base)) {
|
||||
// Strip the base URL from the link
|
||||
url = link.replace(base, '');
|
||||
}
|
||||
|
||||
navigate(url);
|
||||
}
|
||||
};
|
30
src/frontend/lib/functions/Plugins.tsx
Normal file
30
src/frontend/lib/functions/Plugins.tsx
Normal file
@ -0,0 +1,30 @@
|
||||
import {
|
||||
INVENTREE_PLUGIN_VERSION,
|
||||
type InvenTreePluginContext
|
||||
} from '../types/Plugins';
|
||||
|
||||
function extractVersion(version: string) {
|
||||
// Extract the version number from the string
|
||||
const [major, minor, patch] = version
|
||||
.split('.')
|
||||
.map((v) => Number.parseInt(v, 10));
|
||||
|
||||
return { major, minor, patch };
|
||||
}
|
||||
|
||||
/**
|
||||
* Check th e
|
||||
*/
|
||||
export function checkPluginVersion(context: InvenTreePluginContext) {
|
||||
const pluginVersion = extractVersion(INVENTREE_PLUGIN_VERSION);
|
||||
const systemVersion = extractVersion(context.version.inventree);
|
||||
|
||||
const mismatch = `Plugin version mismatch! Expected version ${INVENTREE_PLUGIN_VERSION}, got ${context.version}`;
|
||||
|
||||
// A major version mismatch indicates a potentially breaking change
|
||||
if (pluginVersion.major !== systemVersion.major) {
|
||||
console.warn(mismatch);
|
||||
} else if (INVENTREE_PLUGIN_VERSION != context.version.inventree) {
|
||||
console.info(mismatch);
|
||||
}
|
||||
}
|
23
src/frontend/lib/index.ts
Normal file
23
src/frontend/lib/index.ts
Normal file
@ -0,0 +1,23 @@
|
||||
// Constant value definitions
|
||||
export {
|
||||
INVENTREE_PLUGIN_VERSION,
|
||||
INVENTREE_REACT_VERSION,
|
||||
INVENTREE_REACT_DOM_VERSION,
|
||||
INVENTREE_MANTINE_VERSION
|
||||
} from './types/Plugins';
|
||||
|
||||
// Common type definitions
|
||||
export { ApiEndpoints } from './enums/ApiEndpoints';
|
||||
export { ModelType } from './enums/ModelType';
|
||||
export { UserRoles, UserPermissions } from './enums/Roles';
|
||||
|
||||
export type { InvenTreePluginContext } from './types/Plugins';
|
||||
|
||||
// Common utility functions
|
||||
export { apiUrl } from './functions/Api';
|
||||
export {
|
||||
getBaseUrl,
|
||||
getDetailUrl,
|
||||
navigateToLink
|
||||
} from './functions/Navigation';
|
||||
export { checkPluginVersion } from './functions/Plugins';
|
51
src/frontend/lib/types/Auth.tsx
Normal file
51
src/frontend/lib/types/Auth.tsx
Normal file
@ -0,0 +1,51 @@
|
||||
export interface AuthContext {
|
||||
status: number;
|
||||
data: { flows: Flow[] };
|
||||
meta: { is_authenticated: boolean };
|
||||
}
|
||||
|
||||
export enum FlowEnum {
|
||||
VerifyEmail = 'verify_email',
|
||||
Login = 'login',
|
||||
Signup = 'signup',
|
||||
ProviderRedirect = 'provider_redirect',
|
||||
ProviderSignup = 'provider_signup',
|
||||
ProviderToken = 'provider_token',
|
||||
MfaAuthenticate = 'mfa_authenticate',
|
||||
Reauthenticate = 'reauthenticate',
|
||||
MfaReauthenticate = 'mfa_reauthenticate'
|
||||
}
|
||||
|
||||
export interface Flow {
|
||||
id: FlowEnum;
|
||||
providers?: string[];
|
||||
is_pending?: boolean[];
|
||||
}
|
||||
|
||||
export interface AuthProvider {
|
||||
id: string;
|
||||
name: string;
|
||||
flows: string[];
|
||||
client_id: string;
|
||||
}
|
||||
|
||||
export interface AuthConfig {
|
||||
account: {
|
||||
authentication_method: string;
|
||||
};
|
||||
socialaccount: { providers: AuthProvider[] };
|
||||
mfa: {
|
||||
supported_types: string[];
|
||||
};
|
||||
usersessions: {
|
||||
track_activity: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
// Errors
|
||||
export type ErrorResponse = {
|
||||
data: any;
|
||||
status: number;
|
||||
statusText: string;
|
||||
message?: string;
|
||||
};
|
13
src/frontend/lib/types/Core.tsx
Normal file
13
src/frontend/lib/types/Core.tsx
Normal file
@ -0,0 +1,13 @@
|
||||
import type { MantineSize } from '@mantine/core';
|
||||
|
||||
export type UiSizeType = MantineSize | string | number;
|
||||
|
||||
export interface UserTheme {
|
||||
primaryColor: string;
|
||||
whiteColor: string;
|
||||
blackColor: string;
|
||||
radius: UiSizeType;
|
||||
loader: string;
|
||||
}
|
||||
|
||||
export type PathParams = Record<string, string | number>;
|
69
src/frontend/lib/types/Filters.tsx
Normal file
69
src/frontend/lib/types/Filters.tsx
Normal file
@ -0,0 +1,69 @@
|
||||
import type { ModelType } from '../enums/ModelType';
|
||||
|
||||
/**
|
||||
* Interface for the table filter choice
|
||||
*/
|
||||
export type TableFilterChoice = {
|
||||
value: string;
|
||||
label: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Available filter types
|
||||
*
|
||||
* boolean: A simple true/false filter
|
||||
* choice: A filter which allows selection from a list of (supplied)
|
||||
* date: A filter which allows selection from a date input
|
||||
* text: A filter which allows raw text input
|
||||
* api: A filter which fetches its options from an API endpoint
|
||||
*/
|
||||
export type TableFilterType = 'boolean' | 'choice' | 'date' | 'text' | 'api';
|
||||
|
||||
/**
|
||||
* Interface for the table filter type. Provides a number of options for selecting filter value:
|
||||
*
|
||||
* name: The name of the filter (used for query string)
|
||||
* label: The label to display in the UI (human readable)
|
||||
* description: A description of the filter (human readable)
|
||||
* type: The type of filter (see TableFilterType)
|
||||
* choices: A list of TableFilterChoice objects
|
||||
* choiceFunction: A function which returns a list of TableFilterChoice objects
|
||||
* defaultValue: The default value for the filter
|
||||
* value: The current value of the filter
|
||||
* displayValue: The current display value of the filter
|
||||
* active: Whether the filter is active (false = hidden, not used)
|
||||
* apiUrl: The API URL to use for fetching dynamic filter options
|
||||
* model: The model type to use for fetching dynamic filter options
|
||||
* modelRenderer: A function to render a simple text version of the model type
|
||||
*/
|
||||
export type TableFilter = {
|
||||
name: string;
|
||||
label: string;
|
||||
description?: string;
|
||||
type?: TableFilterType;
|
||||
choices?: TableFilterChoice[];
|
||||
choiceFunction?: () => TableFilterChoice[];
|
||||
defaultValue?: any;
|
||||
value?: any;
|
||||
displayValue?: any;
|
||||
active?: boolean;
|
||||
apiUrl?: string;
|
||||
model?: ModelType;
|
||||
modelRenderer?: (instance: any) => string;
|
||||
};
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
* filterKey: A unique key for the filter set
|
||||
* activeFilters: An array of active filters
|
||||
* setActiveFilters: A function to set the active filters
|
||||
* clearActiveFilters: A function to clear all active filters
|
||||
*/
|
||||
export type FilterSetState = {
|
||||
filterKey: string;
|
||||
activeFilters: TableFilter[];
|
||||
setActiveFilters: (filters: TableFilter[]) => void;
|
||||
clearActiveFilters: () => void;
|
||||
};
|
187
src/frontend/lib/types/Forms.tsx
Normal file
187
src/frontend/lib/types/Forms.tsx
Normal file
@ -0,0 +1,187 @@
|
||||
import type { DefaultMantineColor, MantineStyleProp } from '@mantine/core';
|
||||
import type { UseFormReturnType } from '@mantine/form';
|
||||
import type { ReactNode } from 'react';
|
||||
import type { FieldValues, UseFormReturn } from 'react-hook-form';
|
||||
import type { ApiEndpoints } from '../enums/ApiEndpoints';
|
||||
import type { ModelType } from '../enums/ModelType';
|
||||
import type { PathParams, UiSizeType } from './Core';
|
||||
import type { TableState } from './Tables';
|
||||
|
||||
export interface ApiFormAction {
|
||||
text: string;
|
||||
variant?: 'outline';
|
||||
color?: DefaultMantineColor;
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
export type ApiFormData = UseFormReturnType<Record<string, unknown>>;
|
||||
|
||||
export type ApiFormAdjustFilterType = {
|
||||
filters: any;
|
||||
data: FieldValues;
|
||||
};
|
||||
|
||||
export type ApiFormFieldChoice = {
|
||||
value: any;
|
||||
display_name: string;
|
||||
};
|
||||
|
||||
// Define individual headers in a table field
|
||||
export type ApiFormFieldHeader = {
|
||||
title: string;
|
||||
style?: MantineStyleProp;
|
||||
};
|
||||
|
||||
/** Definition of the ApiForm field component.
|
||||
* - The 'name' attribute *must* be provided
|
||||
* - All other attributes are optional, and may be provided by the API
|
||||
* - However, they can be overridden by the user
|
||||
*
|
||||
* @param name : The name of the field
|
||||
* @param label : The label to display for the field
|
||||
* @param value : The value of the field
|
||||
* @param default : The default value of the field
|
||||
* @param icon : An icon to display next to the field
|
||||
* @param field_type : The type of field to render
|
||||
* @param api_url : The API endpoint to fetch data from (for related fields)
|
||||
* @param pk_field : The primary key field for the related field (default = "pk")
|
||||
* @param model : The model to use for related fields
|
||||
* @param filters : Optional API filters to apply to related fields
|
||||
* @param required : Whether the field is required
|
||||
* @param hidden : Whether the field is hidden
|
||||
* @param disabled : Whether the field is disabled
|
||||
* @param error : Optional error message to display
|
||||
* @param exclude : Whether to exclude the field from the submitted data
|
||||
* @param placeholder : The placeholder text to display
|
||||
* @param description : The description to display for the field
|
||||
* @param preFieldContent : Content to render before the field
|
||||
* @param postFieldContent : Content to render after the field
|
||||
* @param onValueChange : Callback function to call when the field value changes
|
||||
* @param adjustFilters : Callback function to adjust the filters for a related field before a query is made
|
||||
* @param adjustValue : Callback function to adjust the value of the field before it is sent to the API
|
||||
* @param addRow : Callback function to add a new row to a table field
|
||||
* @param onKeyDown : Callback function to get which key was pressed in the form to handle submission on enter
|
||||
*/
|
||||
export type ApiFormFieldType = {
|
||||
label?: string;
|
||||
value?: any;
|
||||
default?: any;
|
||||
icon?: ReactNode;
|
||||
field_type?:
|
||||
| 'related field'
|
||||
| 'email'
|
||||
| 'url'
|
||||
| 'string'
|
||||
| 'icon'
|
||||
| 'boolean'
|
||||
| 'date'
|
||||
| 'datetime'
|
||||
| 'integer'
|
||||
| 'decimal'
|
||||
| 'float'
|
||||
| 'number'
|
||||
| 'choice'
|
||||
| 'file upload'
|
||||
| 'nested object'
|
||||
| 'dependent field'
|
||||
| 'table';
|
||||
api_url?: string;
|
||||
pk_field?: string;
|
||||
model?: ModelType;
|
||||
modelRenderer?: (instance: any) => ReactNode;
|
||||
filters?: any;
|
||||
child?: ApiFormFieldType;
|
||||
children?: { [key: string]: ApiFormFieldType };
|
||||
required?: boolean;
|
||||
error?: string;
|
||||
choices?: ApiFormFieldChoice[];
|
||||
hidden?: boolean;
|
||||
disabled?: boolean;
|
||||
exclude?: boolean;
|
||||
read_only?: boolean;
|
||||
placeholder?: string;
|
||||
description?: string;
|
||||
preFieldContent?: JSX.Element;
|
||||
postFieldContent?: JSX.Element;
|
||||
adjustValue?: (value: any) => any;
|
||||
onValueChange?: (value: any, record?: any) => void;
|
||||
adjustFilters?: (value: ApiFormAdjustFilterType) => any;
|
||||
addRow?: () => any;
|
||||
headers?: ApiFormFieldHeader[];
|
||||
depends_on?: string[];
|
||||
};
|
||||
|
||||
export type ApiFormFieldSet = Record<string, ApiFormFieldType>;
|
||||
|
||||
/**
|
||||
* Properties for the ApiForm component
|
||||
* @param url : The API endpoint to fetch the form data from
|
||||
* @param pk : Optional primary-key value when editing an existing object
|
||||
* @param pk_field : Optional primary-key field name (default: pk)
|
||||
* @param pathParams : Optional path params for the url
|
||||
* @param method : Optional HTTP method to use when submitting the form (default: GET)
|
||||
* @param fields : The fields to render in the form
|
||||
* @param submitText : Optional custom text to display on the submit button (default: Submit)4
|
||||
* @param submitColor : Optional custom color for the submit button (default: green)
|
||||
* @param fetchInitialData : Optional flag to fetch initial data from the server (default: true)
|
||||
* @param preFormContent : Optional content to render before the form fields
|
||||
* @param postFormContent : Optional content to render after the form fields
|
||||
* @param successMessage : Optional message to display on successful form submission
|
||||
* @param onFormSuccess : A callback function to call when the form is submitted successfully.
|
||||
* @param onFormError : A callback function to call when the form is submitted with errors.
|
||||
* @param processFormData : A callback function to process the form data before submission
|
||||
* @param checkClose: A callback function to check if the form can be closed after submission
|
||||
* @param modelType : Define a model type for this form
|
||||
* @param follow : Boolean, follow the result of the form (if possible)
|
||||
* @param table : Table to update on success (if provided)
|
||||
*/
|
||||
export interface ApiFormProps {
|
||||
url: ApiEndpoints | string;
|
||||
pk?: number | string;
|
||||
pk_field?: string;
|
||||
pathParams?: PathParams;
|
||||
queryParams?: URLSearchParams;
|
||||
method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
||||
fields?: ApiFormFieldSet;
|
||||
focus?: string;
|
||||
initialData?: FieldValues;
|
||||
submitText?: string;
|
||||
submitColor?: string;
|
||||
fetchInitialData?: boolean;
|
||||
ignorePermissionCheck?: boolean;
|
||||
preFormContent?: JSX.Element;
|
||||
preFormWarning?: string;
|
||||
preFormSuccess?: string;
|
||||
postFormContent?: JSX.Element;
|
||||
successMessage?: string | null;
|
||||
onFormSuccess?: (data: any, form: UseFormReturn) => void;
|
||||
onFormError?: (response: any, form: UseFormReturn) => void;
|
||||
processFormData?: (data: any, form: UseFormReturn) => any;
|
||||
checkClose?: (data: any, form: UseFormReturn) => boolean;
|
||||
table?: TableState;
|
||||
modelType?: ModelType;
|
||||
follow?: boolean;
|
||||
actions?: ApiFormAction[];
|
||||
timeout?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param title : The title to display in the modal header
|
||||
* @param cancelText : Optional custom text to display on the cancel button (default: Cancel)
|
||||
* @param cancelColor : Optional custom color for the cancel button (default: blue)
|
||||
* @param onClose : A callback function to call when the modal is closed.
|
||||
* @param onOpen : A callback function to call when the modal is opened.
|
||||
*/
|
||||
export interface ApiFormModalProps extends ApiFormProps {
|
||||
title: string;
|
||||
cancelText?: string;
|
||||
cancelColor?: string;
|
||||
onClose?: () => void;
|
||||
onOpen?: () => void;
|
||||
closeOnClickOutside?: boolean;
|
||||
size?: UiSizeType;
|
||||
}
|
||||
|
||||
export interface BulkEditApiFormModalProps extends ApiFormModalProps {
|
||||
items: number[];
|
||||
}
|
9
src/frontend/lib/types/Icons.tsx
Normal file
9
src/frontend/lib/types/Icons.tsx
Normal file
@ -0,0 +1,9 @@
|
||||
import type { Icon, IconProps } from '@tabler/icons-react';
|
||||
|
||||
export type TablerIconType = React.ForwardRefExoticComponent<
|
||||
Omit<IconProps, 'ref'> & React.RefAttributes<Icon>
|
||||
>;
|
||||
|
||||
export type InvenTreeIconType = {
|
||||
[key: string]: TablerIconType;
|
||||
};
|
17
src/frontend/lib/types/Modals.tsx
Normal file
17
src/frontend/lib/types/Modals.tsx
Normal file
@ -0,0 +1,17 @@
|
||||
import type { UiSizeType } from './Core';
|
||||
|
||||
export interface UseModalProps {
|
||||
title: string;
|
||||
children: React.ReactElement;
|
||||
size?: UiSizeType;
|
||||
onOpen?: () => void;
|
||||
onClose?: () => void;
|
||||
closeOnClickOutside?: boolean;
|
||||
}
|
||||
|
||||
export interface UseModalReturn {
|
||||
open: () => void;
|
||||
close: () => void;
|
||||
toggle: () => void;
|
||||
modal: React.ReactElement;
|
||||
}
|
87
src/frontend/lib/types/Plugins.tsx
Normal file
87
src/frontend/lib/types/Plugins.tsx
Normal file
@ -0,0 +1,87 @@
|
||||
import type { MantineColorScheme, MantineTheme } from '@mantine/core';
|
||||
import type { QueryClient } from '@tanstack/react-query';
|
||||
import type { AxiosInstance } from 'axios';
|
||||
import type { NavigateFunction } from 'react-router-dom';
|
||||
import type { ModelType } from '../enums/ModelType';
|
||||
import type { ApiFormModalProps, BulkEditApiFormModalProps } from './Forms';
|
||||
import type { UseModalReturn } from './Modals';
|
||||
import type { SettingsStateProps } from './Settings';
|
||||
import type { UserStateProps } from './User';
|
||||
|
||||
export interface PluginProps {
|
||||
name: string;
|
||||
slug: string;
|
||||
version: null | string;
|
||||
}
|
||||
|
||||
export interface PluginVersion {
|
||||
inventree: string;
|
||||
react: string;
|
||||
reactDom: string;
|
||||
mantine: string;
|
||||
}
|
||||
|
||||
export type InvenTreeFormsContext = {
|
||||
bulkEdit: (props: BulkEditApiFormModalProps) => UseModalReturn;
|
||||
create: (props: ApiFormModalProps) => UseModalReturn;
|
||||
delete: (props: ApiFormModalProps) => UseModalReturn;
|
||||
edit: (props: ApiFormModalProps) => UseModalReturn;
|
||||
};
|
||||
|
||||
/**
|
||||
* A set of properties which are passed to a plugin,
|
||||
* for rendering an element in the user interface.
|
||||
*
|
||||
* @param version - The version of the running InvenTree software stack
|
||||
* @param api - The Axios API instance (see ../states/ApiState.tsx)
|
||||
* @param user - The current user instance (see ../states/UserState.tsx)
|
||||
* @param userSettings - The current user settings (see ../states/SettingsState.tsx)
|
||||
* @param globalSettings - The global settings (see ../states/SettingsState.tsx)
|
||||
* @param navigate - The navigation function (see react-router-dom)
|
||||
* @param theme - The current Mantine theme
|
||||
* @param colorScheme - The current Mantine color scheme (e.g. 'light' / 'dark')
|
||||
* @param host - The current host URL
|
||||
* @param locale - The current locale string (e.g. 'en' / 'de')
|
||||
* @param model - The model type associated with the rendered component (if applicable)
|
||||
* @param id - The ID (primary key) of the model instance for the plugin (if applicable)
|
||||
* @param instance - The model instance data (if available)
|
||||
* @param reloadContent - A function which can be called to reload the plugin content
|
||||
* @param reloadInstance - A function which can be called to reload the model instance
|
||||
* @param context - Any additional context data which may be passed to the plugin
|
||||
*/
|
||||
export type InvenTreePluginContext = {
|
||||
version: PluginVersion;
|
||||
api: AxiosInstance;
|
||||
queryClient: QueryClient;
|
||||
user: UserStateProps;
|
||||
userSettings: SettingsStateProps;
|
||||
globalSettings: SettingsStateProps;
|
||||
host: string;
|
||||
locale: string;
|
||||
navigate: NavigateFunction;
|
||||
theme: MantineTheme;
|
||||
forms: InvenTreeFormsContext;
|
||||
colorScheme: MantineColorScheme;
|
||||
model?: ModelType | string;
|
||||
id?: string | number | null;
|
||||
instance?: any;
|
||||
reloadContent?: () => void;
|
||||
reloadInstance?: () => void;
|
||||
context?: any;
|
||||
};
|
||||
|
||||
/*
|
||||
* The version of the InvenTree plugin context interface.
|
||||
* This number should be incremented if the interface changes.
|
||||
*/
|
||||
|
||||
// @ts-ignore
|
||||
export const INVENTREE_PLUGIN_VERSION: string = __INVENTREE_LIB_VERSION__;
|
||||
// @ts-ignore
|
||||
export const INVENTREE_REACT_VERSION: string = __INVENTREE_REACT_VERSION__;
|
||||
// @ts-ignore
|
||||
export const INVENTREE_REACT_DOM_VERSION: string =
|
||||
// @ts-ignore
|
||||
__INVENTREE_REACT_DOM_VERSION__;
|
||||
// @ts-ignore
|
||||
export const INVENTREE_MANTINE_VERSION: string = __INVENTREE_MANTINE_VERSION__;
|
8
src/frontend/lib/types/Server.tsx
Normal file
8
src/frontend/lib/types/Server.tsx
Normal file
@ -0,0 +1,8 @@
|
||||
export interface Host {
|
||||
host: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface HostList {
|
||||
[key: string]: Host;
|
||||
}
|
55
src/frontend/lib/types/Settings.tsx
Normal file
55
src/frontend/lib/types/Settings.tsx
Normal file
@ -0,0 +1,55 @@
|
||||
import type { ApiEndpoints } from '../enums/ApiEndpoints';
|
||||
import type { PathParams } from './Core';
|
||||
|
||||
export enum SettingTyp {
|
||||
InvenTree = 'inventree',
|
||||
Plugin = 'plugin',
|
||||
User = 'user',
|
||||
Notification = 'notification'
|
||||
}
|
||||
|
||||
export enum SettingType {
|
||||
Boolean = 'boolean',
|
||||
Integer = 'integer',
|
||||
String = 'string',
|
||||
Choice = 'choice',
|
||||
Model = 'related field'
|
||||
}
|
||||
|
||||
// Type interface defining a single 'setting' object
|
||||
export interface Setting {
|
||||
pk: number;
|
||||
key: string;
|
||||
value: string;
|
||||
name: string;
|
||||
description: string;
|
||||
type: SettingType;
|
||||
units: string;
|
||||
choices: SettingChoice[];
|
||||
model_name: string | null;
|
||||
model_filters: Record<string, any> | null;
|
||||
api_url: string | null;
|
||||
typ: SettingTyp;
|
||||
plugin?: string;
|
||||
method?: string;
|
||||
required?: boolean;
|
||||
}
|
||||
|
||||
export interface SettingChoice {
|
||||
value: string;
|
||||
display_name: string;
|
||||
}
|
||||
|
||||
export type SettingsLookup = {
|
||||
[key: string]: string;
|
||||
};
|
||||
|
||||
export interface SettingsStateProps {
|
||||
settings: Setting[];
|
||||
lookup: SettingsLookup;
|
||||
fetchSettings: () => Promise<boolean>;
|
||||
endpoint: ApiEndpoints;
|
||||
pathParams?: PathParams;
|
||||
getSetting: (key: string, default_value?: string) => string; // Return a raw setting value
|
||||
isSet: (key: string, default_value?: boolean) => boolean; // Check a "boolean" setting
|
||||
}
|
69
src/frontend/lib/types/Tables.tsx
Normal file
69
src/frontend/lib/types/Tables.tsx
Normal file
@ -0,0 +1,69 @@
|
||||
import type { SetURLSearchParams } from 'react-router-dom';
|
||||
import type { FilterSetState } from './Filters';
|
||||
|
||||
/*
|
||||
* Type definition for representing the state of a table:
|
||||
*
|
||||
* tableKey: A unique key for the table. When this key changes, the table will be refreshed.
|
||||
* refreshTable: A callback function to externally refresh the table.
|
||||
* isLoading: A boolean flag to indicate if the table is currently loading data
|
||||
* setIsLoading: A function to set the isLoading flag
|
||||
* filterSet: A group of active filters
|
||||
* queryFilters: A map of query filters (e.g. ?active=true&overdue=false) passed in the URL
|
||||
* setQueryFilters: A function to set the query filters
|
||||
* clearQueryFilters: A function to clear all query filters
|
||||
* expandedRecords: An array of expanded records (rows) in the table
|
||||
* setExpandedRecords: A function to set the expanded records
|
||||
* isRowExpanded: A function to determine if a record is expanded
|
||||
* selectedRecords: An array of selected records (rows) in the table
|
||||
* selectedIds: An array of primary key values for selected records
|
||||
* hasSelectedRecords: A boolean flag to indicate if any records are selected
|
||||
* setSelectedRecords: A function to set the selected records
|
||||
* clearSelectedRecords: A function to clear all selected records
|
||||
* hiddenColumns: An array of hidden column names
|
||||
* setHiddenColumns: A function to set the hidden columns
|
||||
* searchTerm: The current search term for the table
|
||||
* setSearchTerm: A function to set the search term
|
||||
* recordCount: The total number of records in the table
|
||||
* setRecordCount: A function to set the record count
|
||||
* page: The current page number
|
||||
* setPage: A function to set the current page number
|
||||
* pageSize: The number of records per page
|
||||
* setPageSize: A function to set the number of records per page
|
||||
* records: An array of records (rows) in the table
|
||||
* setRecords: A function to set the records
|
||||
* updateRecord: A function to update a single record in the table
|
||||
* idAccessor: The name of the primary key field in the records (default = 'pk')
|
||||
*/
|
||||
export type TableState = {
|
||||
tableKey: string;
|
||||
refreshTable: () => void;
|
||||
isLoading: boolean;
|
||||
setIsLoading: (value: boolean) => void;
|
||||
filterSet: FilterSetState;
|
||||
queryFilters: URLSearchParams;
|
||||
setQueryFilters: SetURLSearchParams;
|
||||
clearQueryFilters: () => void;
|
||||
expandedRecords: any[];
|
||||
setExpandedRecords: (records: any[]) => void;
|
||||
isRowExpanded: (pk: number) => boolean;
|
||||
selectedRecords: any[];
|
||||
selectedIds: any[];
|
||||
hasSelectedRecords: boolean;
|
||||
setSelectedRecords: (records: any[]) => void;
|
||||
clearSelectedRecords: () => void;
|
||||
hiddenColumns: string[];
|
||||
setHiddenColumns: (columns: string[]) => void;
|
||||
searchTerm: string;
|
||||
setSearchTerm: (term: string) => void;
|
||||
recordCount: number;
|
||||
setRecordCount: (count: number) => void;
|
||||
page: number;
|
||||
setPage: (page: number) => void;
|
||||
pageSize: number;
|
||||
setPageSize: (pageSize: number) => void;
|
||||
records: any[];
|
||||
setRecords: (records: any[]) => void;
|
||||
updateRecord: (record: any) => void;
|
||||
idAccessor?: string;
|
||||
};
|
62
src/frontend/lib/types/User.tsx
Normal file
62
src/frontend/lib/types/User.tsx
Normal file
@ -0,0 +1,62 @@
|
||||
import type { ModelType } from '../enums/ModelType';
|
||||
import type { UserPermissions, UserRoles } from '../enums/Roles';
|
||||
|
||||
export interface UserProfile {
|
||||
language: string;
|
||||
theme: any;
|
||||
widgets: any;
|
||||
displayname: string | null;
|
||||
position: string | null;
|
||||
status: string | null;
|
||||
location: string | null;
|
||||
active: boolean;
|
||||
contact: string | null;
|
||||
type: string;
|
||||
organisation: string | null;
|
||||
primary_group: number | null;
|
||||
}
|
||||
|
||||
// Type interface fully defining the current user
|
||||
export interface UserProps {
|
||||
pk: number;
|
||||
username: string;
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
email: string;
|
||||
is_staff?: boolean;
|
||||
is_superuser?: boolean;
|
||||
roles?: Record<string, string[]>;
|
||||
permissions?: Record<string, string[]>;
|
||||
groups: any[] | null;
|
||||
profile: UserProfile;
|
||||
}
|
||||
|
||||
export interface UserStateProps {
|
||||
user: UserProps | undefined;
|
||||
is_authed: boolean;
|
||||
userId: () => number | undefined;
|
||||
username: () => string;
|
||||
setAuthenticated: (authed?: boolean) => void;
|
||||
fetchUserToken: () => Promise<void>;
|
||||
setUser: (newUser: UserProps | undefined) => void;
|
||||
getUser: () => UserProps | undefined;
|
||||
fetchUserState: () => Promise<void>;
|
||||
clearUserState: () => void;
|
||||
checkUserRole: (role: UserRoles, permission: UserPermissions) => boolean;
|
||||
hasDeleteRole: (role: UserRoles) => boolean;
|
||||
hasChangeRole: (role: UserRoles) => boolean;
|
||||
hasAddRole: (role: UserRoles) => boolean;
|
||||
hasViewRole: (role: UserRoles) => boolean;
|
||||
checkUserPermission: (
|
||||
model: ModelType,
|
||||
permission: UserPermissions
|
||||
) => boolean;
|
||||
hasDeletePermission: (model: ModelType) => boolean;
|
||||
hasChangePermission: (model: ModelType) => boolean;
|
||||
hasAddPermission: (model: ModelType) => boolean;
|
||||
hasViewPermission: (model: ModelType) => boolean;
|
||||
isAuthed: () => boolean;
|
||||
isLoggedIn: () => boolean;
|
||||
isStaff: () => boolean;
|
||||
isSuperuser: () => boolean;
|
||||
}
|
@ -1,11 +1,40 @@
|
||||
{
|
||||
"name": "inventreeui",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"name": "@inventreedb/ui",
|
||||
"description": "UI components for the InvenTree project",
|
||||
"version": "0.0.8",
|
||||
"private": false,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
"inventree"
|
||||
],
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": {
|
||||
".": "./dist/index.js"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"lib",
|
||||
"LICENSE",
|
||||
"README.md",
|
||||
"CHANGELOG.md"
|
||||
],
|
||||
"homepage": "https://inventree.org",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/inventree/inventree"
|
||||
},
|
||||
"author": {
|
||||
"name": "InvenTree Developers",
|
||||
"email": "support@inventree.org",
|
||||
"url": "https://inventree.org",
|
||||
"org": "InvenTree"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc && vite build",
|
||||
"build": "tsc && vite build --emptyOutDir",
|
||||
"lib": "tsc --p ./tsconfig.lib.json && vite --config vite.lib.config.ts build",
|
||||
"preview": "vite preview",
|
||||
"extract": "lingui extract",
|
||||
"compile": "lingui compile --typescript"
|
||||
@ -101,6 +130,7 @@
|
||||
"typescript": "^5.8.2",
|
||||
"vite": "^6.2.6",
|
||||
"vite-plugin-babel-macros": "^1.0.6",
|
||||
"vite-plugin-dts": "^4.5.3",
|
||||
"vite-plugin-istanbul": "^6.0.2"
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +1,16 @@
|
||||
import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
|
||||
import { ModelInformationDict } from '@lib/enums/ModelInformation';
|
||||
import type { ModelType } from '@lib/enums/ModelType';
|
||||
import { apiUrl } from '@lib/functions/Api';
|
||||
import { getDetailUrl } from '@lib/functions/Navigation';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { Box, Divider, Modal } from '@mantine/core';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { type NavigateFunction, useNavigate } from 'react-router-dom';
|
||||
import { api } from '../../App';
|
||||
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||
import type { ModelType } from '../../enums/ModelType';
|
||||
import { extractErrorMessage } from '../../functions/api';
|
||||
import { getDetailUrl } from '../../functions/urls';
|
||||
import { apiUrl } from '../../states/ApiState';
|
||||
import { useUserState } from '../../states/UserState';
|
||||
import { StylishText } from '../items/StylishText';
|
||||
import { ModelInformationDict } from '../render/ModelType';
|
||||
import { BarcodeInput } from './BarcodeInput';
|
||||
|
||||
export default function BarcodeScanDialog({
|
||||
|
@ -1,4 +1,4 @@
|
||||
import type { ModelType } from '../../enums/ModelType';
|
||||
import type { ModelType } from '@lib/enums/ModelType';
|
||||
|
||||
/**
|
||||
* Interface defining a single barcode scan item
|
||||
|
@ -18,9 +18,9 @@ import { useQuery } from '@tanstack/react-query';
|
||||
import QR from 'qrcode';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
|
||||
import { apiUrl } from '@lib/functions/Api';
|
||||
import { api } from '../../App';
|
||||
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||
import { apiUrl } from '../../states/ApiState';
|
||||
import { useGlobalSettingsState } from '../../states/SettingsState';
|
||||
import { CopyButton } from '../buttons/CopyButton';
|
||||
import type { QrCodeType } from '../items/ActionDropdown';
|
||||
|
@ -2,11 +2,11 @@ import { t } from '@lingui/core/macro';
|
||||
import { IconUserStar } from '@tabler/icons-react';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
|
||||
import type { ModelType } from '../../enums/ModelType';
|
||||
import { ModelInformationDict } from '@lib/enums/ModelInformation';
|
||||
import type { ModelType } from '@lib/enums/ModelType';
|
||||
import { generateUrl } from '../../functions/urls';
|
||||
import { useServerApiState } from '../../states/ApiState';
|
||||
import { useUserState } from '../../states/UserState';
|
||||
import { ModelInformationDict } from '../render/ModelType';
|
||||
import { ActionButton } from './ActionButton';
|
||||
|
||||
export type AdminButtonProps = {
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { Button, Tooltip } from '@mantine/core';
|
||||
|
||||
import { InvenTreeIcon, type InvenTreeIconType } from '../../functions/icons';
|
||||
import type { InvenTreeIconType } from '@lib/types/Icons';
|
||||
import { InvenTreeIcon } from '../../functions/icons';
|
||||
|
||||
/**
|
||||
* A "primary action" button for display on a page detail, (for example)
|
||||
@ -15,7 +16,7 @@ export default function PrimaryActionButton({
|
||||
}: Readonly<{
|
||||
title: string;
|
||||
tooltip?: string;
|
||||
icon?: InvenTreeIconType;
|
||||
icon?: keyof InvenTreeIconType;
|
||||
color?: string;
|
||||
hidden?: boolean;
|
||||
onClick: () => void;
|
||||
|
@ -1,19 +1,19 @@
|
||||
import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
|
||||
import type { ModelType } from '@lib/enums/ModelType';
|
||||
import { apiUrl } from '@lib/functions/Api';
|
||||
import type { ApiFormFieldSet } from '@lib/types/Forms';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { IconPrinter, IconReport, IconTags } from '@tabler/icons-react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { api } from '../../App';
|
||||
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||
import type { ModelType } from '../../enums/ModelType';
|
||||
import { extractAvailableFields } from '../../functions/forms';
|
||||
import useDataOutput from '../../hooks/UseDataOutput';
|
||||
import { useCreateApiFormModal } from '../../hooks/UseForm';
|
||||
import { apiUrl } from '../../states/ApiState';
|
||||
import {
|
||||
useGlobalSettingsState,
|
||||
useUserSettingsState
|
||||
} from '../../states/SettingsState';
|
||||
import type { ApiFormFieldSet } from '../forms/fields/ApiFormField';
|
||||
import { ActionDropdown } from '../items/ActionDropdown';
|
||||
|
||||
export function PrintingActions({
|
||||
|
@ -14,9 +14,9 @@ import {
|
||||
IconLogin
|
||||
} from '@tabler/icons-react';
|
||||
|
||||
import type { AuthProvider } from '@lib/types/Auth';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { ProviderLogin } from '../../functions/auth';
|
||||
import type { Provider } from '../../states/states';
|
||||
|
||||
const brandIcons: { [key: string]: JSX.Element } = {
|
||||
google: <IconBrandGoogle />,
|
||||
@ -32,7 +32,7 @@ const brandIcons: { [key: string]: JSX.Element } = {
|
||||
microsoft: <IconBrandAzure />
|
||||
};
|
||||
|
||||
export function SsoButton({ provider }: Readonly<{ provider: Provider }>) {
|
||||
export function SsoButton({ provider }: Readonly<{ provider: AuthProvider }>) {
|
||||
return (
|
||||
<Tooltip
|
||||
label={t`You will be redirected to the provider for further actions.`}
|
||||
@ -48,6 +48,6 @@ export function SsoButton({ provider }: Readonly<{ provider: Provider }>) {
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
function getBrandIcon(provider: Provider) {
|
||||
function getBrandIcon(provider: AuthProvider) {
|
||||
return brandIcons[provider.id] || <IconLogin />;
|
||||
}
|
||||
|
@ -10,8 +10,8 @@ import {
|
||||
import { IconChevronDown } from '@tabler/icons-react';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import type { TablerIconType } from '@lib/types/Icons';
|
||||
import { identifierString } from '../../functions/conversion';
|
||||
import type { TablerIconType } from '../../functions/icons';
|
||||
import * as classes from './SplitButton.css';
|
||||
|
||||
interface SplitButtonOption {
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
|
||||
import { ModelType } from '@lib/enums/ModelType';
|
||||
import { apiUrl } from '@lib/functions/Api';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { showNotification } from '@mantine/notifications';
|
||||
import { IconBell } from '@tabler/icons-react';
|
||||
import { useApi } from '../../contexts/ApiContext';
|
||||
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||
import { ModelType } from '../../enums/ModelType';
|
||||
import { apiUrl } from '../../states/ApiState';
|
||||
import { ActionButton } from './ActionButton';
|
||||
|
||||
export default function StarredToggleButton({
|
||||
|
@ -4,6 +4,7 @@ import dayGridPlugin from '@fullcalendar/daygrid';
|
||||
import interactionPlugin from '@fullcalendar/interaction';
|
||||
import FullCalendar from '@fullcalendar/react';
|
||||
|
||||
import type { TableFilter } from '@lib/types/Filters';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import {
|
||||
ActionIcon,
|
||||
@ -27,7 +28,6 @@ import {
|
||||
import { useCallback, useState } from 'react';
|
||||
import type { CalendarState } from '../../hooks/UseCalendar';
|
||||
import { useLocalState } from '../../states/LocalState';
|
||||
import type { TableFilter } from '../../tables/Filter';
|
||||
import { FilterSelectDrawer } from '../../tables/FilterSelectDrawer';
|
||||
import { TableSearchInput } from '../../tables/Search';
|
||||
import { Boundary } from '../Boundary';
|
||||
|
@ -3,6 +3,13 @@ import type {
|
||||
EventClickArg,
|
||||
EventContentArg
|
||||
} from '@fullcalendar/core';
|
||||
import { ModelInformationDict } from '@lib/enums/ModelInformation';
|
||||
import type { ModelType } from '@lib/enums/ModelType';
|
||||
import type { UserRoles } from '@lib/enums/Roles';
|
||||
import { apiUrl } from '@lib/functions/Api';
|
||||
import { getDetailUrl } from '@lib/functions/Navigation';
|
||||
import { navigateToLink } from '@lib/functions/Navigation';
|
||||
import type { TableFilter } from '@lib/types/Filters';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { ActionIcon, Group, Text } from '@mantine/core';
|
||||
import { hideNotification, showNotification } from '@mantine/notifications';
|
||||
@ -15,22 +22,15 @@ import dayjs from 'dayjs';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { api } from '../../App';
|
||||
import type { ModelType } from '../../enums/ModelType';
|
||||
import type { UserRoles } from '../../enums/Roles';
|
||||
import { navigateToLink } from '../../functions/navigation';
|
||||
import { getDetailUrl } from '../../functions/urls';
|
||||
import useCalendar from '../../hooks/UseCalendar';
|
||||
import { apiUrl } from '../../states/ApiState';
|
||||
import { useUserState } from '../../states/UserState';
|
||||
import {
|
||||
AssignedToMeFilter,
|
||||
HasProjectCodeFilter,
|
||||
OrderStatusFilter,
|
||||
ProjectCodeFilter,
|
||||
ResponsibleFilter,
|
||||
type TableFilter
|
||||
ResponsibleFilter
|
||||
} from '../../tables/Filter';
|
||||
import { ModelInformationDict } from '../render/ModelType';
|
||||
import { StatusRenderer } from '../render/StatusRenderer';
|
||||
import Calendar from './Calendar';
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { t } from '@lingui/core/macro';
|
||||
|
||||
import { ModelType } from '../../enums/ModelType';
|
||||
import { ModelType } from '@lib/enums/ModelType';
|
||||
import { useGlobalSettingsState } from '../../states/SettingsState';
|
||||
import type { DashboardWidgetProps } from './DashboardWidget';
|
||||
import ColorToggleDashboardWidget from './widgets/ColorToggleWidget';
|
||||
|
@ -14,10 +14,10 @@ import { IconMailCheck } from '@tabler/icons-react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
|
||||
import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
|
||||
import { apiUrl } from '@lib/functions/Api';
|
||||
import { api } from '../../../App';
|
||||
import { formatDate } from '../../../defaults/formatters';
|
||||
import { ApiEndpoints } from '../../../enums/ApiEndpoints';
|
||||
import { apiUrl } from '../../../states/ApiState';
|
||||
import { useUserState } from '../../../states/UserState';
|
||||
import { StylishText } from '../../items/StylishText';
|
||||
|
||||
|
@ -4,17 +4,15 @@ import { useQuery } from '@tanstack/react-query';
|
||||
import { type ReactNode, useCallback } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { ModelInformationDict } from '@lib/enums/ModelInformation';
|
||||
import type { ModelType } from '@lib/enums/ModelType';
|
||||
import { apiUrl } from '@lib/functions/Api';
|
||||
import { navigateToLink } from '@lib/functions/Navigation';
|
||||
import type { InvenTreeIconType } from '@lib/types/Icons';
|
||||
import { useApi } from '../../../contexts/ApiContext';
|
||||
import type { ModelType } from '../../../enums/ModelType';
|
||||
import {
|
||||
InvenTreeIcon,
|
||||
type InvenTreeIconType
|
||||
} from '../../../functions/icons';
|
||||
import { navigateToLink } from '../../../functions/navigation';
|
||||
import { apiUrl } from '../../../states/ApiState';
|
||||
import { InvenTreeIcon } from '../../../functions/icons';
|
||||
import { useUserState } from '../../../states/UserState';
|
||||
import { StylishText } from '../../items/StylishText';
|
||||
import { ModelInformationDict } from '../../render/ModelType';
|
||||
import type { DashboardWidgetProps } from '../DashboardWidget';
|
||||
|
||||
/**
|
||||
@ -28,7 +26,7 @@ function QueryCountWidget({
|
||||
}: Readonly<{
|
||||
modelType: ModelType;
|
||||
title: string;
|
||||
icon?: InvenTreeIconType;
|
||||
icon?: keyof InvenTreeIconType;
|
||||
params: any;
|
||||
}>): ReactNode {
|
||||
const api = useApi();
|
||||
|
@ -17,14 +17,15 @@ import { getValueAtPath } from 'mantine-datatable';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
|
||||
import { ModelType } from '@lib/enums/ModelType';
|
||||
import { apiUrl } from '@lib/functions/Api';
|
||||
import { getDetailUrl } from '@lib/functions/Navigation';
|
||||
import { navigateToLink } from '@lib/functions/Navigation';
|
||||
import type { InvenTreeIconType } from '@lib/types/Icons';
|
||||
import { useApi } from '../../contexts/ApiContext';
|
||||
import { formatDate } from '../../defaults/formatters';
|
||||
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||
import { ModelType } from '../../enums/ModelType';
|
||||
import { InvenTreeIcon, type InvenTreeIconType } from '../../functions/icons';
|
||||
import { navigateToLink } from '../../functions/navigation';
|
||||
import { getDetailUrl } from '../../functions/urls';
|
||||
import { apiUrl } from '../../states/ApiState';
|
||||
import { InvenTreeIcon } from '../../functions/icons';
|
||||
import { useGlobalSettingsState } from '../../states/SettingsState';
|
||||
import { CopyButton } from '../buttons/CopyButton';
|
||||
import { YesNoButton } from '../buttons/YesNoButton';
|
||||
@ -35,7 +36,7 @@ import { StatusRenderer } from '../render/StatusRenderer';
|
||||
|
||||
export type DetailsField = {
|
||||
hidden?: boolean;
|
||||
icon?: InvenTreeIconType;
|
||||
icon?: keyof InvenTreeIconType;
|
||||
name: string;
|
||||
label?: string;
|
||||
badge?: BadgeType;
|
||||
@ -464,7 +465,7 @@ export function DetailsTableField({
|
||||
<Table.Td style={{ minWidth: 75, lineBreak: 'auto', flex: 2 }}>
|
||||
<Group gap='xs' wrap='nowrap'>
|
||||
<InvenTreeIcon
|
||||
icon={field.icon ?? (field.name as InvenTreeIconType)}
|
||||
icon={field.icon ?? (field.name as keyof InvenTreeIconType)}
|
||||
/>
|
||||
<Text style={{ paddingLeft: 10 }}>{field.label}</Text>
|
||||
</Group>
|
||||
|
@ -21,10 +21,10 @@ import { useHover } from '@mantine/hooks';
|
||||
import { modals } from '@mantine/modals';
|
||||
import { useMemo, useState } from 'react';
|
||||
|
||||
import type { UserRoles } from '@lib/enums/Roles';
|
||||
import { cancelEvent } from '@lib/functions/Events';
|
||||
import { showNotification } from '@mantine/notifications';
|
||||
import { api } from '../../App';
|
||||
import type { UserRoles } from '../../enums/Roles';
|
||||
import { cancelEvent } from '../../functions/events';
|
||||
import { InvenTreeIcon } from '../../functions/icons';
|
||||
import { showApiErrorMessage } from '../../functions/notifications';
|
||||
import { useEditApiFormModal } from '../../hooks/UseForm';
|
||||
|
@ -7,11 +7,11 @@ import 'easymde/dist/easymde.min.css';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import SimpleMDE from 'react-simplemde-editor';
|
||||
|
||||
import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
|
||||
import { ModelInformationDict } from '@lib/enums/ModelInformation';
|
||||
import type { ModelType } from '@lib/enums/ModelType';
|
||||
import { apiUrl } from '@lib/functions/Api';
|
||||
import { useApi } from '../../contexts/ApiContext';
|
||||
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||
import type { ModelType } from '../../enums/ModelType';
|
||||
import { apiUrl } from '../../states/ApiState';
|
||||
import { ModelInformationDict } from '../render/ModelType';
|
||||
|
||||
/*
|
||||
* A text editor component for editing notes against a model type and instance.
|
||||
|
@ -24,14 +24,14 @@ import Split from '@uiw/react-split';
|
||||
import type React from 'react';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import { ModelInformationDict } from '@lib/enums/ModelInformation';
|
||||
import { ModelType } from '@lib/enums/ModelType';
|
||||
import { apiUrl } from '@lib/functions/Api';
|
||||
import { api } from '../../../App';
|
||||
import { ModelType } from '../../../enums/ModelType';
|
||||
import { apiUrl } from '../../../states/ApiState';
|
||||
import type { TemplateI } from '../../../tables/settings/TemplateTable';
|
||||
import { Boundary } from '../../Boundary';
|
||||
import { SplitButton } from '../../buttons/SplitButton';
|
||||
import { StandaloneField } from '../../forms/StandaloneField';
|
||||
import { ModelInformationDict } from '../../render/ModelType';
|
||||
|
||||
type EditorProps = {
|
||||
template: TemplateI;
|
||||
|
@ -2,7 +2,6 @@ import { t } from '@lingui/core/macro';
|
||||
import {
|
||||
Alert,
|
||||
Button,
|
||||
type DefaultMantineColor,
|
||||
Divider,
|
||||
Group,
|
||||
LoadingOverlay,
|
||||
@ -19,14 +18,17 @@ import {
|
||||
FormProvider,
|
||||
type SubmitErrorHandler,
|
||||
type SubmitHandler,
|
||||
type UseFormReturn,
|
||||
useForm
|
||||
} from 'react-hook-form';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { type NavigateFunction, useNavigate } from 'react-router-dom';
|
||||
|
||||
import { getDetailUrl } from '@lib/functions/Navigation';
|
||||
import type {
|
||||
ApiFormFieldSet,
|
||||
ApiFormFieldType,
|
||||
ApiFormProps
|
||||
} from '@lib/types/Forms';
|
||||
import { useApi } from '../../contexts/ApiContext';
|
||||
import type { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||
import type { ModelType } from '../../enums/ModelType';
|
||||
import {
|
||||
type NestedDict,
|
||||
constructField,
|
||||
@ -38,74 +40,8 @@ import {
|
||||
invalidResponse,
|
||||
showTimeoutNotification
|
||||
} from '../../functions/notifications';
|
||||
import { getDetailUrl } from '../../functions/urls';
|
||||
import type { TableState } from '../../hooks/UseTable';
|
||||
import type { PathParams } from '../../states/ApiState';
|
||||
import { Boundary } from '../Boundary';
|
||||
import {
|
||||
ApiFormField,
|
||||
type ApiFormFieldSet,
|
||||
type ApiFormFieldType
|
||||
} from './fields/ApiFormField';
|
||||
|
||||
export interface ApiFormAction {
|
||||
text: string;
|
||||
variant?: 'outline';
|
||||
color?: DefaultMantineColor;
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Properties for the ApiForm component
|
||||
* @param url : The API endpoint to fetch the form data from
|
||||
* @param pk : Optional primary-key value when editing an existing object
|
||||
* @param pk_field : Optional primary-key field name (default: pk)
|
||||
* @param pathParams : Optional path params for the url
|
||||
* @param method : Optional HTTP method to use when submitting the form (default: GET)
|
||||
* @param fields : The fields to render in the form
|
||||
* @param submitText : Optional custom text to display on the submit button (default: Submit)4
|
||||
* @param submitColor : Optional custom color for the submit button (default: green)
|
||||
* @param fetchInitialData : Optional flag to fetch initial data from the server (default: true)
|
||||
* @param preFormContent : Optional content to render before the form fields
|
||||
* @param postFormContent : Optional content to render after the form fields
|
||||
* @param successMessage : Optional message to display on successful form submission
|
||||
* @param onFormSuccess : A callback function to call when the form is submitted successfully.
|
||||
* @param onFormError : A callback function to call when the form is submitted with errors.
|
||||
* @param processFormData : A callback function to process the form data before submission
|
||||
* @param checkClose: A callback function to check if the form can be closed after submission
|
||||
* @param modelType : Define a model type for this form
|
||||
* @param follow : Boolean, follow the result of the form (if possible)
|
||||
* @param table : Table to update on success (if provided)
|
||||
*/
|
||||
export interface ApiFormProps {
|
||||
url: ApiEndpoints | string;
|
||||
pk?: number | string;
|
||||
pk_field?: string;
|
||||
pathParams?: PathParams;
|
||||
queryParams?: URLSearchParams;
|
||||
method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
||||
fields?: ApiFormFieldSet;
|
||||
focus?: string;
|
||||
initialData?: FieldValues;
|
||||
submitText?: string;
|
||||
submitColor?: string;
|
||||
fetchInitialData?: boolean;
|
||||
ignorePermissionCheck?: boolean;
|
||||
preFormContent?: JSX.Element;
|
||||
preFormWarning?: string;
|
||||
preFormSuccess?: string;
|
||||
postFormContent?: JSX.Element;
|
||||
successMessage?: string | null;
|
||||
onFormSuccess?: (data: any, form: UseFormReturn) => void;
|
||||
onFormError?: (response: any, form: UseFormReturn) => void;
|
||||
processFormData?: (data: any, form: UseFormReturn) => any;
|
||||
checkClose?: (data: any, form: UseFormReturn) => boolean;
|
||||
table?: TableState;
|
||||
modelType?: ModelType;
|
||||
follow?: boolean;
|
||||
actions?: ApiFormAction[];
|
||||
timeout?: number;
|
||||
}
|
||||
import { ApiFormField } from './fields/ApiFormField';
|
||||
|
||||
export function OptionsApiForm({
|
||||
props: _props,
|
||||
@ -219,7 +155,16 @@ export function ApiForm({
|
||||
}>) {
|
||||
const api = useApi();
|
||||
const queryClient = useQueryClient();
|
||||
const navigate = useNavigate();
|
||||
|
||||
// Accessor for the navigation function (which is used to redirect the user)
|
||||
let navigate: NavigateFunction | null = null;
|
||||
|
||||
try {
|
||||
navigate = useNavigate();
|
||||
} catch (_error) {
|
||||
// Note: If we launch a form within a plugin context, useNavigate() may not be available
|
||||
navigate = null;
|
||||
}
|
||||
|
||||
const [fields, setFields] = useState<ApiFormFieldSet>(
|
||||
() => props.fields ?? {}
|
||||
@ -482,7 +427,9 @@ export function ApiForm({
|
||||
|
||||
if (props.follow && props.modelType && response.data?.pk) {
|
||||
// If we want to automatically follow the returned data
|
||||
navigate(getDetailUrl(props.modelType, response.data?.pk));
|
||||
if (!!navigate) {
|
||||
navigate(getDetailUrl(props.modelType, response.data?.pk));
|
||||
}
|
||||
} else if (props.table) {
|
||||
// If we want to automatically update or reload a linked table
|
||||
const pk_field = props.pk_field ?? 'pk';
|
||||
|
@ -15,9 +15,10 @@ import { useDisclosure } from '@mantine/hooks';
|
||||
import { useState } from 'react';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
|
||||
import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
|
||||
import { apiUrl } from '@lib/functions/Api';
|
||||
import { showNotification } from '@mantine/notifications';
|
||||
import { api } from '../../App';
|
||||
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||
import {
|
||||
doBasicLogin,
|
||||
doSimpleLogin,
|
||||
@ -25,7 +26,7 @@ import {
|
||||
followRedirect
|
||||
} from '../../functions/auth';
|
||||
import { showLoginNotification } from '../../functions/notifications';
|
||||
import { apiUrl, useServerApiState } from '../../states/ApiState';
|
||||
import { useServerApiState } from '../../states/ApiState';
|
||||
import { useUserState } from '../../states/UserState';
|
||||
import { SsoButton } from '../buttons/SSOButton';
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
import type { HostList } from '@lib/types/Server';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import {
|
||||
@ -13,8 +14,6 @@ import { useForm } from '@mantine/form';
|
||||
import { randomId } from '@mantine/hooks';
|
||||
import { IconSquarePlus, IconTrash } from '@tabler/icons-react';
|
||||
|
||||
import type { HostList } from '../../states/states';
|
||||
|
||||
export function HostOptionsForm({
|
||||
data,
|
||||
saveOptions
|
||||
|
@ -20,10 +20,10 @@ import {
|
||||
IconServerSpark
|
||||
} from '@tabler/icons-react';
|
||||
|
||||
import type { HostList } from '@lib/types/Server';
|
||||
import { Wrapper } from '../../pages/Auth/Layout';
|
||||
import { useServerApiState } from '../../states/ApiState';
|
||||
import { useLocalState } from '../../states/LocalState';
|
||||
import type { HostList } from '../../states/states';
|
||||
import { ActionButton } from '../buttons/ActionButton';
|
||||
import { HostOptionsForm } from './HostOptionsForm';
|
||||
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
|
||||
import { ApiFormField, type ApiFormFieldType } from './fields/ApiFormField';
|
||||
import type { ApiFormFieldType } from '@lib/types/Forms';
|
||||
import { ApiFormField } from './fields/ApiFormField';
|
||||
|
||||
export function StandaloneField({
|
||||
fieldDefinition,
|
||||
|
@ -1,18 +1,10 @@
|
||||
import { t } from '@lingui/core/macro';
|
||||
import {
|
||||
Alert,
|
||||
FileInput,
|
||||
type MantineStyleProp,
|
||||
NumberInput,
|
||||
Stack,
|
||||
Switch
|
||||
} from '@mantine/core';
|
||||
import type { UseFormReturnType } from '@mantine/form';
|
||||
import { Alert, FileInput, NumberInput, Stack, Switch } from '@mantine/core';
|
||||
import { useId } from '@mantine/hooks';
|
||||
import { type ReactNode, useCallback, useEffect, useMemo } from 'react';
|
||||
import { useCallback, useEffect, useMemo } from 'react';
|
||||
import { type Control, type FieldValues, useController } from 'react-hook-form';
|
||||
|
||||
import type { ModelType } from '../../../enums/ModelType';
|
||||
import type { ApiFormFieldSet, ApiFormFieldType } from '@lib/types/Forms';
|
||||
import { isTrue } from '../../../functions/conversion';
|
||||
import { ChoiceField } from './ChoiceField';
|
||||
import DateField from './DateField';
|
||||
@ -23,103 +15,6 @@ import { RelatedModelField } from './RelatedModelField';
|
||||
import { TableField } from './TableField';
|
||||
import TextField from './TextField';
|
||||
|
||||
export type ApiFormData = UseFormReturnType<Record<string, unknown>>;
|
||||
|
||||
export type ApiFormAdjustFilterType = {
|
||||
filters: any;
|
||||
data: FieldValues;
|
||||
};
|
||||
|
||||
export type ApiFormFieldChoice = {
|
||||
value: any;
|
||||
display_name: string;
|
||||
};
|
||||
|
||||
// Define individual headers in a table field
|
||||
export type ApiFormFieldHeader = {
|
||||
title: string;
|
||||
style?: MantineStyleProp;
|
||||
};
|
||||
|
||||
/** Definition of the ApiForm field component.
|
||||
* - The 'name' attribute *must* be provided
|
||||
* - All other attributes are optional, and may be provided by the API
|
||||
* - However, they can be overridden by the user
|
||||
*
|
||||
* @param name : The name of the field
|
||||
* @param label : The label to display for the field
|
||||
* @param value : The value of the field
|
||||
* @param default : The default value of the field
|
||||
* @param icon : An icon to display next to the field
|
||||
* @param field_type : The type of field to render
|
||||
* @param api_url : The API endpoint to fetch data from (for related fields)
|
||||
* @param pk_field : The primary key field for the related field (default = "pk")
|
||||
* @param model : The model to use for related fields
|
||||
* @param filters : Optional API filters to apply to related fields
|
||||
* @param required : Whether the field is required
|
||||
* @param hidden : Whether the field is hidden
|
||||
* @param disabled : Whether the field is disabled
|
||||
* @param error : Optional error message to display
|
||||
* @param exclude : Whether to exclude the field from the submitted data
|
||||
* @param placeholder : The placeholder text to display
|
||||
* @param description : The description to display for the field
|
||||
* @param preFieldContent : Content to render before the field
|
||||
* @param postFieldContent : Content to render after the field
|
||||
* @param onValueChange : Callback function to call when the field value changes
|
||||
* @param adjustFilters : Callback function to adjust the filters for a related field before a query is made
|
||||
* @param adjustValue : Callback function to adjust the value of the field before it is sent to the API
|
||||
* @param addRow : Callback function to add a new row to a table field
|
||||
* @param onKeyDown : Callback function to get which key was pressed in the form to handle submission on enter
|
||||
*/
|
||||
export type ApiFormFieldType = {
|
||||
label?: string;
|
||||
value?: any;
|
||||
default?: any;
|
||||
icon?: ReactNode;
|
||||
field_type?:
|
||||
| 'related field'
|
||||
| 'email'
|
||||
| 'url'
|
||||
| 'string'
|
||||
| 'icon'
|
||||
| 'boolean'
|
||||
| 'date'
|
||||
| 'datetime'
|
||||
| 'integer'
|
||||
| 'decimal'
|
||||
| 'float'
|
||||
| 'number'
|
||||
| 'choice'
|
||||
| 'file upload'
|
||||
| 'nested object'
|
||||
| 'dependent field'
|
||||
| 'table';
|
||||
api_url?: string;
|
||||
pk_field?: string;
|
||||
model?: ModelType;
|
||||
modelRenderer?: (instance: any) => ReactNode;
|
||||
filters?: any;
|
||||
child?: ApiFormFieldType;
|
||||
children?: { [key: string]: ApiFormFieldType };
|
||||
required?: boolean;
|
||||
error?: string;
|
||||
choices?: ApiFormFieldChoice[];
|
||||
hidden?: boolean;
|
||||
disabled?: boolean;
|
||||
exclude?: boolean;
|
||||
read_only?: boolean;
|
||||
placeholder?: string;
|
||||
description?: string;
|
||||
preFieldContent?: JSX.Element;
|
||||
postFieldContent?: JSX.Element;
|
||||
adjustValue?: (value: any) => any;
|
||||
onValueChange?: (value: any, record?: any) => void;
|
||||
adjustFilters?: (value: ApiFormAdjustFilterType) => any;
|
||||
addRow?: () => any;
|
||||
headers?: ApiFormFieldHeader[];
|
||||
depends_on?: string[];
|
||||
};
|
||||
|
||||
/**
|
||||
* Render an individual form field
|
||||
*/
|
||||
@ -384,5 +279,3 @@ export function ApiFormField({
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
export type ApiFormFieldSet = Record<string, ApiFormFieldType>;
|
||||
|
@ -1,10 +1,9 @@
|
||||
import type { ApiFormFieldType } from '@lib/types/Forms';
|
||||
import { Select } from '@mantine/core';
|
||||
import { useId } from '@mantine/hooks';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import type { FieldValues, UseControllerReturn } from 'react-hook-form';
|
||||
|
||||
import type { ApiFormFieldType } from './ApiFormField';
|
||||
|
||||
/**
|
||||
* Render a 'select' field for selecting from a list of choices
|
||||
*/
|
||||
|
@ -1,11 +1,10 @@
|
||||
import type { ApiFormFieldType } from '@lib/types/Forms';
|
||||
import { DateInput } from '@mantine/dates';
|
||||
import dayjs from 'dayjs';
|
||||
import customParseFormat from 'dayjs/plugin/customParseFormat';
|
||||
import { useCallback, useId, useMemo } from 'react';
|
||||
import type { FieldValues, UseControllerReturn } from 'react-hook-form';
|
||||
|
||||
import type { ApiFormFieldType } from './ApiFormField';
|
||||
|
||||
dayjs.extend(customParseFormat);
|
||||
|
||||
export default function DateField({
|
||||
|
@ -5,16 +5,13 @@ import {
|
||||
useFormContext
|
||||
} from 'react-hook-form';
|
||||
|
||||
import type { ApiFormFieldSet, ApiFormFieldType } from '@lib/types/Forms';
|
||||
import { useApi } from '../../../contexts/ApiContext';
|
||||
import {
|
||||
constructField,
|
||||
extractAvailableFields
|
||||
} from '../../../functions/forms';
|
||||
import {
|
||||
ApiFormField,
|
||||
type ApiFormFieldSet,
|
||||
type ApiFormFieldType
|
||||
} from './ApiFormField';
|
||||
import { ApiFormField } from './ApiFormField';
|
||||
|
||||
export function DependentField({
|
||||
control,
|
||||
|
@ -21,9 +21,9 @@ import { startTransition, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import type { FieldValues, UseControllerReturn } from 'react-hook-form';
|
||||
import { FixedSizeGrid as Grid } from 'react-window';
|
||||
|
||||
import type { ApiFormFieldType } from '@lib/types/Forms';
|
||||
import { useIconState } from '../../../states/IconState';
|
||||
import { ApiIcon } from '../../items/ApiIcon';
|
||||
import type { ApiFormFieldType } from './ApiFormField';
|
||||
|
||||
export default function IconField({
|
||||
controller,
|
||||
|
@ -1,11 +1,7 @@
|
||||
import type { ApiFormFieldSet, ApiFormFieldType } from '@lib/types/Forms';
|
||||
import { Accordion, Divider, Stack, Text } from '@mantine/core';
|
||||
import type { Control, FieldValues } from 'react-hook-form';
|
||||
|
||||
import {
|
||||
ApiFormField,
|
||||
type ApiFormFieldSet,
|
||||
type ApiFormFieldType
|
||||
} from './ApiFormField';
|
||||
import { ApiFormField } from './ApiFormField';
|
||||
|
||||
export function NestedObjectField({
|
||||
control,
|
||||
|
@ -15,10 +15,10 @@ import {
|
||||
} from 'react-hook-form';
|
||||
import Select from 'react-select';
|
||||
|
||||
import type { ApiFormFieldType } from '@lib/types/Forms';
|
||||
import { useApi } from '../../../contexts/ApiContext';
|
||||
import { vars } from '../../../theme';
|
||||
import { RenderInstance } from '../../render/Instance';
|
||||
import type { ApiFormFieldType } from './ApiFormField';
|
||||
|
||||
/**
|
||||
* Render a 'select' field for searching the database against a particular model type
|
||||
|
@ -5,11 +5,11 @@ import { IconExclamationCircle } from '@tabler/icons-react';
|
||||
import { type ReactNode, useCallback, useEffect, useMemo } from 'react';
|
||||
import type { FieldValues, UseControllerReturn } from 'react-hook-form';
|
||||
|
||||
import type { ApiFormFieldType } from '@lib/types/Forms';
|
||||
import { identifierString } from '../../../functions/conversion';
|
||||
import { InvenTreeIcon } from '../../../functions/icons';
|
||||
import { AddItemButton } from '../../buttons/AddItemButton';
|
||||
import { StandaloneField } from '../StandaloneField';
|
||||
import type { ApiFormFieldType } from './ApiFormField';
|
||||
|
||||
export interface TableFieldRowProps {
|
||||
item: any;
|
||||
|
@ -9,18 +9,19 @@ import {
|
||||
} from '@tabler/icons-react';
|
||||
import { type ReactNode, useCallback, useMemo, useState } from 'react';
|
||||
|
||||
import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
|
||||
import { apiUrl } from '@lib/functions/Api';
|
||||
import { cancelEvent } from '@lib/functions/Events';
|
||||
import type { TableFilter } from '@lib/types/Filters';
|
||||
import type { ApiFormFieldSet } from '@lib/types/Forms';
|
||||
import { useApi } from '../../contexts/ApiContext';
|
||||
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||
import { cancelEvent } from '../../functions/events';
|
||||
import {
|
||||
useDeleteApiFormModal,
|
||||
useEditApiFormModal
|
||||
} from '../../hooks/UseForm';
|
||||
import type { ImportSessionState } from '../../hooks/UseImportSession';
|
||||
import { useTable } from '../../hooks/UseTable';
|
||||
import { apiUrl } from '../../states/ApiState';
|
||||
import type { TableColumn } from '../../tables/Column';
|
||||
import type { TableFilter } from '../../tables/Filter';
|
||||
import { InvenTreeTable } from '../../tables/InvenTreeTable';
|
||||
import {
|
||||
type RowAction,
|
||||
@ -29,7 +30,6 @@ import {
|
||||
} from '../../tables/RowActions';
|
||||
import { ActionButton } from '../buttons/ActionButton';
|
||||
import { YesNoButton } from '../buttons/YesNoButton';
|
||||
import type { ApiFormFieldSet } from '../forms/fields/ApiFormField';
|
||||
import { ProgressBar } from '../items/ProgressBar';
|
||||
import { RenderRemoteInstance } from '../render/Instance';
|
||||
|
||||
|
@ -13,12 +13,12 @@ import {
|
||||
import { IconCheck } from '@tabler/icons-react';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
|
||||
import { apiUrl } from '@lib/functions/Api';
|
||||
import type { ApiFormFieldType } from '@lib/types/Forms';
|
||||
import { useApi } from '../../contexts/ApiContext';
|
||||
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||
import type { ImportSessionState } from '../../hooks/UseImportSession';
|
||||
import { apiUrl } from '../../states/ApiState';
|
||||
import { StandaloneField } from '../forms/StandaloneField';
|
||||
import type { ApiFormFieldType } from '../forms/fields/ApiFormField';
|
||||
|
||||
function ImporterColumn({
|
||||
column,
|
||||
|
@ -16,7 +16,7 @@ import {
|
||||
import { IconCheck } from '@tabler/icons-react';
|
||||
import { type ReactNode, useMemo } from 'react';
|
||||
|
||||
import { ModelType } from '../../enums/ModelType';
|
||||
import { ModelType } from '@lib/enums/ModelType';
|
||||
import { useImportSession } from '../../hooks/UseImportSession';
|
||||
import useStatusCodes from '../../hooks/UseStatusCodes';
|
||||
import { StylishText } from '../items/StylishText';
|
||||
|
@ -3,7 +3,7 @@ import { Center, Container, Loader, Stack, Text } from '@mantine/core';
|
||||
import { useInterval } from '@mantine/hooks';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { ModelType } from '../../enums/ModelType';
|
||||
import { ModelType } from '@lib/enums/ModelType';
|
||||
import type { ImportSessionState } from '../../hooks/UseImportSession';
|
||||
import useStatusCodes from '../../hooks/UseStatusCodes';
|
||||
import { StylishText } from '../items/StylishText';
|
||||
|
@ -20,7 +20,7 @@ import {
|
||||
} from '@tabler/icons-react';
|
||||
import { type ReactNode, useMemo } from 'react';
|
||||
|
||||
import type { ModelType } from '../../enums/ModelType';
|
||||
import type { ModelType } from '@lib/enums/ModelType';
|
||||
import { identifierString } from '../../functions/conversion';
|
||||
import { InvenTreeIcon } from '../../functions/icons';
|
||||
import { InvenTreeQRCode, QRCodeLink, QRCodeUnlink } from '../barcodes/QRCode';
|
||||
|
@ -11,15 +11,16 @@ import {
|
||||
import { useMemo } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { InvenTreeIcon, type InvenTreeIconType } from '../../functions/icons';
|
||||
import { navigateToLink } from '../../functions/navigation';
|
||||
import { navigateToLink } from '@lib/functions/Navigation';
|
||||
import type { InvenTreeIconType } from '@lib/types/Icons';
|
||||
import { InvenTreeIcon } from '../../functions/icons';
|
||||
import { StylishText } from './StylishText';
|
||||
|
||||
export interface MenuLinkItem {
|
||||
id: string;
|
||||
title: string | JSX.Element;
|
||||
description?: string;
|
||||
icon?: InvenTreeIconType;
|
||||
icon?: keyof InvenTreeIconType;
|
||||
action?: () => void;
|
||||
link?: string;
|
||||
external?: boolean;
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
|
||||
import { apiUrl } from '@lib/functions/Api';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import {
|
||||
@ -13,8 +15,6 @@ import { notifications } from '@mantine/notifications';
|
||||
import { IconCircleCheck, IconReload } from '@tabler/icons-react';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { api } from '../../App';
|
||||
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||
import { apiUrl } from '../../states/ApiState';
|
||||
|
||||
export interface RuleSet {
|
||||
pk?: number;
|
||||
|
@ -3,33 +3,19 @@ import {
|
||||
Text,
|
||||
darken,
|
||||
getThemeColor,
|
||||
lighten,
|
||||
useMantineColorScheme,
|
||||
useMantineTheme
|
||||
} from '@mantine/core';
|
||||
import { useMemo } from 'react';
|
||||
import { useLocalState } from '../../states/LocalState';
|
||||
|
||||
// Hook that memoizes the gradient color based on the primary color of the theme
|
||||
const useThematicGradient = () => {
|
||||
const { usertheme } = useLocalState();
|
||||
const theme = useMantineTheme();
|
||||
const colorScheme = useMantineColorScheme();
|
||||
|
||||
const primary = useMemo(() => {
|
||||
return getThemeColor(usertheme.primaryColor, theme);
|
||||
}, [usertheme.primaryColor, theme]);
|
||||
return getThemeColor(theme.primaryColor, theme);
|
||||
}, [theme]);
|
||||
|
||||
const secondary = useMemo(() => {
|
||||
let secondary = primary;
|
||||
if (colorScheme.colorScheme == 'dark') {
|
||||
secondary = lighten(primary, 0.3);
|
||||
} else {
|
||||
secondary = darken(primary, 0.3);
|
||||
}
|
||||
|
||||
return secondary;
|
||||
}, [usertheme, colorScheme, primary]);
|
||||
const secondary = useMemo(() => darken(primary, 0.25), [primary]);
|
||||
|
||||
return useMemo(() => {
|
||||
return { primary, secondary };
|
||||
|
@ -14,10 +14,11 @@ import {
|
||||
import type { ContextModalProps } from '@mantine/modals';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
|
||||
import { apiUrl } from '@lib/functions/Api';
|
||||
import { api } from '../../App';
|
||||
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||
import { generateUrl } from '../../functions/urls';
|
||||
import { apiUrl, useServerApiState } from '../../states/ApiState';
|
||||
import { useServerApiState } from '../../states/ApiState';
|
||||
import { useUserState } from '../../states/UserState';
|
||||
import { CopyButton } from '../buttons/CopyButton';
|
||||
import { StylishText } from '../items/StylishText';
|
||||
|
@ -14,9 +14,9 @@ import {
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
|
||||
import { apiUrl } from '@lib/functions/Api';
|
||||
import { api } from '../../App';
|
||||
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||
import { apiUrl } from '../../states/ApiState';
|
||||
|
||||
export function LicenceView(entries: Readonly<any[]>) {
|
||||
return (
|
||||
|
@ -10,8 +10,8 @@ import { IconMenu2 } from '@tabler/icons-react';
|
||||
import { useMemo } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { navigateToLink } from '@lib/functions/Navigation';
|
||||
import { identifierString } from '../../functions/conversion';
|
||||
import { navigateToLink } from '../../functions/navigation';
|
||||
|
||||
export type Breadcrumb = {
|
||||
icon?: React.ReactNode;
|
||||
|
@ -4,7 +4,7 @@ import { useCallback, useMemo } from 'react';
|
||||
import { Link, Route, Routes, useNavigate, useParams } from 'react-router-dom';
|
||||
import type { To } from 'react-router-dom';
|
||||
|
||||
import type { UiSizeType } from '../../defaults/formatters';
|
||||
import type { UiSizeType } from '@lib/types/Core';
|
||||
import { useLocalState } from '../../states/LocalState';
|
||||
import { StylishText } from '../items/StylishText';
|
||||
import * as classes from './DetailDrawer.css';
|
||||
|
@ -13,13 +13,14 @@ import { useQuery } from '@tanstack/react-query';
|
||||
import { type ReactNode, useEffect, useMemo, useState } from 'react';
|
||||
import { useMatch, useNavigate } from 'react-router-dom';
|
||||
|
||||
import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
|
||||
import { apiUrl } from '@lib/functions/Api';
|
||||
import { navigateToLink } from '@lib/functions/Navigation';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { api } from '../../App';
|
||||
import { getNavTabs } from '../../defaults/links';
|
||||
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||
import { navigateToLink } from '../../functions/navigation';
|
||||
import * as classes from '../../main.css';
|
||||
import { apiUrl, useServerApiState } from '../../states/ApiState';
|
||||
import { useServerApiState } from '../../states/ApiState';
|
||||
import { useLocalState } from '../../states/LocalState';
|
||||
import {
|
||||
useGlobalSettingsState,
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { LoadingOverlay } from '@mantine/core';
|
||||
|
||||
import type { ModelType } from '../../enums/ModelType';
|
||||
import type { UserRoles } from '../../enums/Roles';
|
||||
import type { ModelType } from '@lib/enums/ModelType';
|
||||
import type { UserRoles } from '@lib/enums/Roles';
|
||||
import { useUserState } from '../../states/UserState';
|
||||
import ClientError from '../errors/ClientError';
|
||||
import PermissionDenied from '../errors/PermissionDenied';
|
||||
|
@ -10,9 +10,9 @@ import {
|
||||
import { useViewportSize } from '@mantine/hooks';
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import { ModelType } from '@lib/enums/ModelType';
|
||||
import { UserRoles } from '@lib/enums/Roles';
|
||||
import { AboutLinks, DocumentationLinks } from '../../defaults/links';
|
||||
import { ModelType } from '../../enums/ModelType';
|
||||
import { UserRoles } from '../../enums/Roles';
|
||||
import useInstanceName from '../../hooks/UseInstanceName';
|
||||
import * as classes from '../../main.css';
|
||||
import { useGlobalSettingsState } from '../../states/SettingsState';
|
||||
|
@ -21,12 +21,12 @@ import { useQuery } from '@tanstack/react-query';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import type { ApiEndpoints } from '@lib/enums/ApiEndpoints';
|
||||
import type { ModelType } from '@lib/enums/ModelType';
|
||||
import { apiUrl } from '@lib/functions/Api';
|
||||
import { getDetailUrl } from '@lib/functions/Navigation';
|
||||
import { navigateToLink } from '@lib/functions/Navigation';
|
||||
import { useApi } from '../../contexts/ApiContext';
|
||||
import type { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||
import type { ModelType } from '../../enums/ModelType';
|
||||
import { navigateToLink } from '../../functions/navigation';
|
||||
import { getDetailUrl } from '../../functions/urls';
|
||||
import { apiUrl } from '../../states/ApiState';
|
||||
import { ApiIcon } from '../items/ApiIcon';
|
||||
import { StylishText } from '../items/StylishText';
|
||||
|
||||
|
@ -18,17 +18,17 @@ import { useQuery } from '@tanstack/react-query';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
|
||||
import { ModelInformationDict } from '@lib/enums/ModelInformation';
|
||||
import type { ModelType } from '@lib/enums/ModelType';
|
||||
import { apiUrl } from '@lib/functions/Api';
|
||||
import { getDetailUrl } from '@lib/functions/Navigation';
|
||||
import { getBaseUrl } from '@lib/functions/Navigation';
|
||||
import { navigateToLink } from '@lib/functions/Navigation';
|
||||
import { api } from '../../App';
|
||||
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||
import type { ModelType } from '../../enums/ModelType';
|
||||
import { navigateToLink } from '../../functions/navigation';
|
||||
import { getDetailUrl } from '../../functions/urls';
|
||||
import { getBaseUrl } from '../../main';
|
||||
import { apiUrl } from '../../states/ApiState';
|
||||
import { useUserState } from '../../states/UserState';
|
||||
import { Boundary } from '../Boundary';
|
||||
import { StylishText } from '../items/StylishText';
|
||||
import { ModelInformationDict } from '../render/ModelType';
|
||||
|
||||
/**
|
||||
* Render a single notification entry in the drawer
|
||||
|
@ -32,19 +32,20 @@ import { useQuery } from '@tanstack/react-query';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { type NavigateFunction, useNavigate } from 'react-router-dom';
|
||||
|
||||
import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
|
||||
import { ModelInformationDict } from '@lib/enums/ModelInformation';
|
||||
import { ModelType } from '@lib/enums/ModelType';
|
||||
import { UserRoles } from '@lib/enums/Roles';
|
||||
import { apiUrl } from '@lib/functions/Api';
|
||||
import { cancelEvent } from '@lib/functions/Events';
|
||||
import { navigateToLink } from '@lib/functions/Navigation';
|
||||
import { showNotification } from '@mantine/notifications';
|
||||
import { api } from '../../App';
|
||||
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||
import { ModelType } from '../../enums/ModelType';
|
||||
import { UserRoles } from '../../enums/Roles';
|
||||
import { cancelEvent } from '../../functions/events';
|
||||
import { navigateToLink } from '../../functions/navigation';
|
||||
import { apiUrl } from '../../states/ApiState';
|
||||
import { useUserSettingsState } from '../../states/SettingsState';
|
||||
import { useUserState } from '../../states/UserState';
|
||||
import { Boundary } from '../Boundary';
|
||||
import { RenderInstance } from '../render/Instance';
|
||||
import { ModelInformationDict, getModelInfo } from '../render/ModelType';
|
||||
import { getModelInfo } from '../render/ModelType';
|
||||
|
||||
// Define type for handling individual search queries
|
||||
type SearchQuery = {
|
||||
|
@ -2,7 +2,7 @@ import { t } from '@lingui/core/macro';
|
||||
import { Skeleton } from '@mantine/core';
|
||||
import { IconPaperclip } from '@tabler/icons-react';
|
||||
|
||||
import type { ModelType } from '../../enums/ModelType';
|
||||
import type { ModelType } from '@lib/enums/ModelType';
|
||||
import { AttachmentTable } from '../../tables/general/AttachmentTable';
|
||||
import type { PanelType } from './Panel';
|
||||
|
||||
|
@ -2,7 +2,7 @@ import { t } from '@lingui/core/macro';
|
||||
import { Skeleton } from '@mantine/core';
|
||||
import { IconNotes } from '@tabler/icons-react';
|
||||
|
||||
import type { ModelType } from '../../enums/ModelType';
|
||||
import type { ModelType } from '@lib/enums/ModelType';
|
||||
import { useUserState } from '../../states/UserState';
|
||||
import NotesEditor from '../editors/NotesEditor';
|
||||
import type { PanelType } from './Panel';
|
||||
|
@ -28,10 +28,10 @@ import {
|
||||
useParams
|
||||
} from 'react-router-dom';
|
||||
|
||||
import type { ModelType } from '../../enums/ModelType';
|
||||
import type { ModelType } from '@lib/enums/ModelType';
|
||||
import { cancelEvent } from '@lib/functions/Events';
|
||||
import { navigateToLink } from '@lib/functions/Navigation';
|
||||
import { identifierString } from '../../functions/conversion';
|
||||
import { cancelEvent } from '../../functions/events';
|
||||
import { navigateToLink } from '../../functions/navigation';
|
||||
import { usePluginPanels } from '../../hooks/UsePluginPanels';
|
||||
import { useLocalState } from '../../states/LocalState';
|
||||
import { Boundary } from '../Boundary';
|
||||
@ -47,6 +47,7 @@ import * as classes from './PanelGroup.css';
|
||||
* @param model - The target model for this panel group (e.g. 'part' / 'salesorder')
|
||||
* @param id - The target ID for this panel group (set to *null* for groups which do not target a specific model instance)
|
||||
* @param instance - The target model instance for this panel group
|
||||
* @param reloadInstance - Function to reload the model instance
|
||||
* @param selectedPanel - The currently selected panel
|
||||
* @param onPanelChange - Callback when the active panel changes
|
||||
* @param collapsible - If true, the panel group can be collapsed (defaults to true)
|
||||
@ -55,6 +56,7 @@ export type PanelProps = {
|
||||
pageKey: string;
|
||||
panels: PanelType[];
|
||||
instance?: any;
|
||||
reloadInstance?: () => void;
|
||||
model?: ModelType | string;
|
||||
id?: number | null;
|
||||
selectedPanel?: string;
|
||||
@ -67,6 +69,7 @@ function BasePanelGroup({
|
||||
panels,
|
||||
onPanelChange,
|
||||
selectedPanel,
|
||||
reloadInstance,
|
||||
instance,
|
||||
model,
|
||||
id,
|
||||
@ -82,9 +85,10 @@ function BasePanelGroup({
|
||||
|
||||
// Hook to load plugins for this panel
|
||||
const pluginPanelSet = usePluginPanels({
|
||||
id: id,
|
||||
model: model,
|
||||
instance: instance,
|
||||
id: id
|
||||
reloadFunc: reloadInstance
|
||||
});
|
||||
|
||||
// Rebuild the list of panels
|
||||
|
@ -1,12 +1,12 @@
|
||||
import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
|
||||
import { apiUrl } from '@lib/functions/Api';
|
||||
import type { ApiFormFieldSet } from '@lib/types/Forms';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { IconRadar } from '@tabler/icons-react';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||
import { useCreateApiFormModal } from '../../hooks/UseForm';
|
||||
import { usePluginsWithMixin } from '../../hooks/UsePlugins';
|
||||
import { apiUrl } from '../../states/ApiState';
|
||||
import { ActionButton } from '../buttons/ActionButton';
|
||||
import type { ApiFormFieldSet } from '../forms/fields/ApiFormField';
|
||||
import type { PluginInterface } from './PluginInterface';
|
||||
|
||||
export default function LocateItemButton({
|
||||
|
@ -1,51 +1,27 @@
|
||||
import {
|
||||
type MantineColorScheme,
|
||||
type MantineTheme,
|
||||
useMantineColorScheme,
|
||||
useMantineTheme
|
||||
} from '@mantine/core';
|
||||
import type { AxiosInstance } from 'axios';
|
||||
import { useMantineColorScheme, useMantineTheme } from '@mantine/core';
|
||||
import { useMemo } from 'react';
|
||||
import { type NavigateFunction, useNavigate } from 'react-router-dom';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import type { QueryClient } from '@tanstack/react-query';
|
||||
import { api, queryClient } from '../../App';
|
||||
import { useLocalState } from '../../states/LocalState';
|
||||
import {
|
||||
type SettingsStateProps,
|
||||
useGlobalSettingsState,
|
||||
useUserSettingsState
|
||||
} from '../../states/SettingsState';
|
||||
import { type UserStateProps, useUserState } from '../../states/UserState';
|
||||
import { useUserState } from '../../states/UserState';
|
||||
|
||||
/**
|
||||
* A set of properties which are passed to a plugin,
|
||||
* for rendering an element in the user interface.
|
||||
*
|
||||
* @param api - The Axios API instance (see ../states/ApiState.tsx)
|
||||
* @param user - The current user instance (see ../states/UserState.tsx)
|
||||
* @param userSettings - The current user settings (see ../states/SettingsState.tsx)
|
||||
* @param globalSettings - The global settings (see ../states/SettingsState.tsx)
|
||||
* @param navigate - The navigation function (see react-router-dom)
|
||||
* @param theme - The current Mantine theme
|
||||
* @param colorScheme - The current Mantine color scheme (e.g. 'light' / 'dark')
|
||||
* @param host - The current host URL
|
||||
* @param locale - The current locale string (e.g. 'en' / 'de')
|
||||
* @param context - Any additional context data which may be passed to the plugin
|
||||
*/
|
||||
export type InvenTreeContext = {
|
||||
api: AxiosInstance;
|
||||
queryClient: QueryClient;
|
||||
user: UserStateProps;
|
||||
userSettings: SettingsStateProps;
|
||||
globalSettings: SettingsStateProps;
|
||||
host: string;
|
||||
locale: string;
|
||||
navigate: NavigateFunction;
|
||||
theme: MantineTheme;
|
||||
colorScheme: MantineColorScheme;
|
||||
context?: any;
|
||||
};
|
||||
import {
|
||||
INVENTREE_MANTINE_VERSION,
|
||||
INVENTREE_PLUGIN_VERSION,
|
||||
INVENTREE_REACT_VERSION,
|
||||
type InvenTreePluginContext
|
||||
} from '@lib/types/Plugins';
|
||||
import {
|
||||
useBulkEditApiFormModal,
|
||||
useCreateApiFormModal,
|
||||
useDeleteApiFormModal,
|
||||
useEditApiFormModal
|
||||
} from '../../hooks/UseForm';
|
||||
|
||||
export const useInvenTreeContext = () => {
|
||||
const [locale, host] = useLocalState((s) => [s.language, s.host]);
|
||||
@ -56,8 +32,14 @@ export const useInvenTreeContext = () => {
|
||||
const globalSettings = useGlobalSettingsState();
|
||||
const userSettings = useUserSettingsState();
|
||||
|
||||
const contextData = useMemo<InvenTreeContext>(() => {
|
||||
const contextData = useMemo<InvenTreePluginContext>(() => {
|
||||
return {
|
||||
version: {
|
||||
inventree: INVENTREE_PLUGIN_VERSION,
|
||||
react: INVENTREE_REACT_VERSION,
|
||||
reactDom: INVENTREE_REACT_VERSION,
|
||||
mantine: INVENTREE_MANTINE_VERSION
|
||||
},
|
||||
user: user,
|
||||
host: host,
|
||||
locale: locale,
|
||||
@ -67,7 +49,13 @@ export const useInvenTreeContext = () => {
|
||||
globalSettings: globalSettings,
|
||||
userSettings: userSettings,
|
||||
theme: theme,
|
||||
colorScheme: colorScheme
|
||||
colorScheme: colorScheme,
|
||||
forms: {
|
||||
bulkEdit: useBulkEditApiFormModal,
|
||||
create: useCreateApiFormModal,
|
||||
delete: useDeleteApiFormModal,
|
||||
edit: useEditApiFormModal
|
||||
}
|
||||
};
|
||||
}, [
|
||||
user,
|
||||
|
@ -4,7 +4,7 @@ import { IconExclamationCircle } from '@tabler/icons-react';
|
||||
import { useMemo } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||
import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
|
||||
import { useInstance } from '../../hooks/UseInstance';
|
||||
import { InfoItem } from '../items/InfoItem';
|
||||
import { StylishText } from '../items/StylishText';
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Stack } from '@mantine/core';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
import type { InvenTreeContext } from './PluginContext';
|
||||
import type { InvenTreePluginContext } from '@lib/types/Plugins';
|
||||
import type { PluginUIFeature } from './PluginUIFeature';
|
||||
import RemoteComponent from './RemoteComponent';
|
||||
|
||||
@ -23,7 +23,7 @@ export default function PluginPanelContent({
|
||||
pluginContext
|
||||
}: Readonly<{
|
||||
pluginFeature: PluginUIFeature;
|
||||
pluginContext: InvenTreeContext;
|
||||
pluginContext: InvenTreePluginContext;
|
||||
}>): ReactNode {
|
||||
return (
|
||||
<Stack gap='xs'>
|
||||
|
@ -1,3 +1,4 @@
|
||||
import type { InvenTreePluginContext } from '@lib/types/Plugins';
|
||||
import { useInvenTreeContext } from './PluginContext';
|
||||
import RemoteComponent from './RemoteComponent';
|
||||
|
||||
@ -21,13 +22,13 @@ export default function PluginSettingsPanel({
|
||||
}: Readonly<{
|
||||
pluginAdmin: PluginAdminInterface;
|
||||
}>) {
|
||||
const pluginContext = useInvenTreeContext();
|
||||
const ctx: InvenTreePluginContext = useInvenTreeContext();
|
||||
|
||||
return (
|
||||
<RemoteComponent
|
||||
source={pluginAdmin.source}
|
||||
defaultFunctionName='renderPluginSettings'
|
||||
context={{ ...pluginContext, context: pluginAdmin.context }}
|
||||
context={{ ...ctx, context: pluginAdmin.context }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
import type { ModelType } from '../../enums/ModelType';
|
||||
import type { InvenTreeIconType } from '../../functions/icons';
|
||||
import type { ModelType } from '@lib/enums/ModelType';
|
||||
import type { InvenTreeIconType } from '@lib/types/Icons';
|
||||
import type { InvenTreePluginContext } from '@lib/types/Plugins';
|
||||
import type { TemplateI } from '../../tables/settings/TemplateTable';
|
||||
import type { TemplateEditorProps } from '../editors/TemplateEditor/TemplateEditor';
|
||||
import type { InvenTreeContext } from './PluginContext';
|
||||
import type { PluginUIFeature } from './PluginUIFeature';
|
||||
|
||||
// #region Type Helpers
|
||||
@ -19,7 +19,7 @@ export type PluginUIGetFeatureType<
|
||||
ServerContext extends Record<string, unknown>
|
||||
> = (params: {
|
||||
featureContext: T['featureContext'];
|
||||
inventreeContext: InvenTreeContext;
|
||||
inventreeContext: InvenTreePluginContext;
|
||||
serverContext: ServerContext;
|
||||
}) => T['featureReturnType'];
|
||||
|
||||
|
@ -1,11 +1,15 @@
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { Alert, Stack, Text } from '@mantine/core';
|
||||
import { Alert, MantineProvider, Stack, Text } from '@mantine/core';
|
||||
import { IconExclamationCircle } from '@tabler/icons-react';
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import type { InvenTreePluginContext } from '@lib/types/Plugins';
|
||||
import { type Root, createRoot } from 'react-dom/client';
|
||||
import { api, queryClient } from '../../App';
|
||||
import { ApiProvider } from '../../contexts/ApiContext';
|
||||
import { LanguageContext } from '../../contexts/LanguageContext';
|
||||
import { identifierString } from '../../functions/conversion';
|
||||
import { Boundary } from '../Boundary';
|
||||
import type { InvenTreeContext } from './PluginContext';
|
||||
import { findExternalPluginFunction } from './PluginSource';
|
||||
|
||||
/**
|
||||
@ -24,9 +28,16 @@ export default function RemoteComponent({
|
||||
}: Readonly<{
|
||||
source: string;
|
||||
defaultFunctionName: string;
|
||||
context: InvenTreeContext;
|
||||
context: InvenTreePluginContext;
|
||||
}>) {
|
||||
const componentRef = useRef<HTMLDivElement>();
|
||||
const [rootElement, setRootElement] = useState<Root | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (componentRef.current && !rootElement) {
|
||||
setRootElement(createRoot(componentRef.current));
|
||||
}
|
||||
}, [componentRef.current]);
|
||||
|
||||
const [renderingError, setRenderingError] = useState<string | undefined>(
|
||||
undefined
|
||||
@ -47,20 +58,45 @@ export default function RemoteComponent({
|
||||
return defaultFunctionName;
|
||||
}, [source, defaultFunctionName]);
|
||||
|
||||
const reloadPluginContent = async () => {
|
||||
if (!componentRef.current) {
|
||||
const reloadPluginContent = useCallback(() => {
|
||||
if (!rootElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ctx: InvenTreePluginContext = {
|
||||
...context,
|
||||
reloadContent: reloadPluginContent
|
||||
};
|
||||
|
||||
if (sourceFile && functionName) {
|
||||
findExternalPluginFunction(sourceFile, functionName)
|
||||
.then((func) => {
|
||||
if (func) {
|
||||
if (!!func) {
|
||||
try {
|
||||
func(componentRef.current, context);
|
||||
if (func.length > 1) {
|
||||
// Support "legacy" plugin functions which call createRoot() internally
|
||||
// Ref: https://github.com/inventree/InvenTree/pull/9439/
|
||||
func(componentRef.current, ctx);
|
||||
} else {
|
||||
// Render the plugin component into the target element
|
||||
// Note that we have to provide the right context(s) to the component
|
||||
// This approach ensures that the component is rendered in the correct context tree
|
||||
rootElement.render(
|
||||
<ApiProvider client={queryClient} api={api}>
|
||||
<MantineProvider
|
||||
theme={ctx.theme}
|
||||
defaultColorScheme={ctx.colorScheme}
|
||||
>
|
||||
<LanguageContext>{func(ctx)}</LanguageContext>
|
||||
</MantineProvider>
|
||||
</ApiProvider>
|
||||
);
|
||||
}
|
||||
|
||||
setRenderingError('');
|
||||
} catch (error) {
|
||||
setRenderingError(`${error}`);
|
||||
console.error(error);
|
||||
}
|
||||
} else {
|
||||
setRenderingError(`${sourceFile}:${functionName}`);
|
||||
@ -76,12 +112,12 @@ export default function RemoteComponent({
|
||||
`${t`Invalid source or function name`} - ${sourceFile}:${functionName}`
|
||||
);
|
||||
}
|
||||
};
|
||||
}, [componentRef, rootElement, sourceFile, functionName, context]);
|
||||
|
||||
// Reload the plugin content dynamically
|
||||
useEffect(() => {
|
||||
reloadPluginContent();
|
||||
}, [sourceFile, functionName, context]);
|
||||
}, [sourceFile, functionName, context, rootElement]);
|
||||
|
||||
return (
|
||||
<Boundary
|
||||
@ -99,7 +135,7 @@ export default function RemoteComponent({
|
||||
</Text>
|
||||
</Alert>
|
||||
)}
|
||||
<div ref={componentRef as any} />
|
||||
{componentRef && <div ref={componentRef as any} />}
|
||||
</Stack>
|
||||
</Boundary>
|
||||
);
|
||||
|
@ -1,7 +1,7 @@
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
import { ModelType } from '../../enums/ModelType';
|
||||
import { getDetailUrl } from '../../functions/urls';
|
||||
import { ModelType } from '@lib/enums/ModelType';
|
||||
import { getDetailUrl } from '@lib/functions/Navigation';
|
||||
import { type InstanceRenderInterface, RenderInlineModel } from './Instance';
|
||||
import { StatusRenderer } from './StatusRenderer';
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { Text } from '@mantine/core';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
import { ModelType } from '../../enums/ModelType';
|
||||
import { getDetailUrl } from '../../functions/urls';
|
||||
import { ModelType } from '@lib/enums/ModelType';
|
||||
import { getDetailUrl } from '@lib/functions/Navigation';
|
||||
import { type InstanceRenderInterface, RenderInlineModel } from './Instance';
|
||||
|
||||
/**
|
||||
|
@ -3,11 +3,12 @@ import { Alert, Anchor, Group, Skeleton, Space, Text } from '@mantine/core';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { type ReactNode, useCallback } from 'react';
|
||||
|
||||
import { ModelInformationDict } from '@lib/enums/ModelInformation';
|
||||
import { ModelType } from '@lib/enums/ModelType';
|
||||
import { apiUrl } from '@lib/functions/Api';
|
||||
import { navigateToLink } from '@lib/functions/Navigation';
|
||||
import { useApi } from '../../contexts/ApiContext';
|
||||
import { ModelType } from '../../enums/ModelType';
|
||||
import { navigateToLink } from '../../functions/navigation';
|
||||
import { shortenString } from '../../functions/tables';
|
||||
import { apiUrl } from '../../states/ApiState';
|
||||
import { Thumbnail } from '../images/Thumbnail';
|
||||
import { RenderBuildItem, RenderBuildLine, RenderBuildOrder } from './Build';
|
||||
import {
|
||||
@ -24,7 +25,6 @@ import {
|
||||
RenderProjectCode,
|
||||
RenderSelectionList
|
||||
} from './Generic';
|
||||
import { ModelInformationDict } from './ModelType';
|
||||
import {
|
||||
RenderPurchaseOrder,
|
||||
RenderReturnOrder,
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { Loader } from '@mantine/core';
|
||||
import { useMemo, useState } from 'react';
|
||||
|
||||
import type { ModelType } from '@lib/enums/ModelType';
|
||||
import { api } from '../../App';
|
||||
import type { ModelType } from '../../enums/ModelType';
|
||||
import { RenderInstance } from './Instance';
|
||||
|
||||
/**
|
||||
|
@ -1,283 +1,8 @@
|
||||
import { t } from '@lingui/core/macro';
|
||||
|
||||
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||
import type { ModelType } from '../../enums/ModelType';
|
||||
import type { InvenTreeIconType } from '../../functions/icons';
|
||||
|
||||
export interface ModelInformationInterface {
|
||||
label: string;
|
||||
label_multiple: string;
|
||||
url_overview?: string;
|
||||
url_detail?: string;
|
||||
api_endpoint: ApiEndpoints;
|
||||
admin_url?: string;
|
||||
icon: InvenTreeIconType;
|
||||
}
|
||||
|
||||
export interface TranslatableModelInformationInterface
|
||||
extends Omit<ModelInformationInterface, 'label' | 'label_multiple'> {
|
||||
label: () => string;
|
||||
label_multiple: () => string;
|
||||
}
|
||||
|
||||
export type ModelDict = {
|
||||
[key in keyof typeof ModelType]: TranslatableModelInformationInterface;
|
||||
};
|
||||
|
||||
export const ModelInformationDict: ModelDict = {
|
||||
part: {
|
||||
label: () => t`Part`,
|
||||
label_multiple: () => t`Parts`,
|
||||
url_overview: '/part/category/index/parts',
|
||||
url_detail: '/part/:pk/',
|
||||
api_endpoint: ApiEndpoints.part_list,
|
||||
admin_url: '/part/part/',
|
||||
icon: 'part'
|
||||
},
|
||||
partparametertemplate: {
|
||||
label: () => t`Part Parameter Template`,
|
||||
label_multiple: () => t`Part Parameter Templates`,
|
||||
url_detail: '/partparametertemplate/:pk/',
|
||||
api_endpoint: ApiEndpoints.part_parameter_template_list,
|
||||
icon: 'test_templates'
|
||||
},
|
||||
parttesttemplate: {
|
||||
label: () => t`Part Test Template`,
|
||||
label_multiple: () => t`Part Test Templates`,
|
||||
url_detail: '/parttesttemplate/:pk/',
|
||||
api_endpoint: ApiEndpoints.part_test_template_list,
|
||||
icon: 'test'
|
||||
},
|
||||
supplierpart: {
|
||||
label: () => t`Supplier Part`,
|
||||
label_multiple: () => t`Supplier Parts`,
|
||||
url_overview: '/purchasing/index/supplier-parts',
|
||||
url_detail: '/purchasing/supplier-part/:pk/',
|
||||
api_endpoint: ApiEndpoints.supplier_part_list,
|
||||
admin_url: '/company/supplierpart/',
|
||||
icon: 'supplier_part'
|
||||
},
|
||||
manufacturerpart: {
|
||||
label: () => t`Manufacturer Part`,
|
||||
label_multiple: () => t`Manufacturer Parts`,
|
||||
url_overview: '/purchasing/index/manufacturer-parts',
|
||||
url_detail: '/purchasing/manufacturer-part/:pk/',
|
||||
api_endpoint: ApiEndpoints.manufacturer_part_list,
|
||||
admin_url: '/company/manufacturerpart/',
|
||||
icon: 'manufacturers'
|
||||
},
|
||||
partcategory: {
|
||||
label: () => t`Part Category`,
|
||||
label_multiple: () => t`Part Categories`,
|
||||
url_overview: '/part/category/parts/subcategories',
|
||||
url_detail: '/part/category/:pk/',
|
||||
api_endpoint: ApiEndpoints.category_list,
|
||||
admin_url: '/part/partcategory/',
|
||||
icon: 'category'
|
||||
},
|
||||
stockitem: {
|
||||
label: () => t`Stock Item`,
|
||||
label_multiple: () => t`Stock Items`,
|
||||
url_overview: '/stock/location/index/stock-items',
|
||||
url_detail: '/stock/item/:pk/',
|
||||
api_endpoint: ApiEndpoints.stock_item_list,
|
||||
admin_url: '/stock/stockitem/',
|
||||
icon: 'stock'
|
||||
},
|
||||
stocklocation: {
|
||||
label: () => t`Stock Location`,
|
||||
label_multiple: () => t`Stock Locations`,
|
||||
url_overview: '/stock/location',
|
||||
url_detail: '/stock/location/:pk/',
|
||||
api_endpoint: ApiEndpoints.stock_location_list,
|
||||
admin_url: '/stock/stocklocation/',
|
||||
icon: 'location'
|
||||
},
|
||||
stocklocationtype: {
|
||||
label: () => t`Stock Location Type`,
|
||||
label_multiple: () => t`Stock Location Types`,
|
||||
api_endpoint: ApiEndpoints.stock_location_type_list,
|
||||
icon: 'location'
|
||||
},
|
||||
stockhistory: {
|
||||
label: () => t`Stock History`,
|
||||
label_multiple: () => t`Stock Histories`,
|
||||
api_endpoint: ApiEndpoints.stock_tracking_list,
|
||||
icon: 'history'
|
||||
},
|
||||
build: {
|
||||
label: () => t`Build`,
|
||||
label_multiple: () => t`Builds`,
|
||||
url_overview: '/manufacturing/index/buildorders/',
|
||||
url_detail: '/manufacturing/build-order/:pk/',
|
||||
api_endpoint: ApiEndpoints.build_order_list,
|
||||
admin_url: '/build/build/',
|
||||
icon: 'build_order'
|
||||
},
|
||||
buildline: {
|
||||
label: () => t`Build Line`,
|
||||
label_multiple: () => t`Build Lines`,
|
||||
url_overview: '/build/line',
|
||||
url_detail: '/build/line/:pk/',
|
||||
api_endpoint: ApiEndpoints.build_line_list,
|
||||
icon: 'build_order'
|
||||
},
|
||||
builditem: {
|
||||
label: () => t`Build Item`,
|
||||
label_multiple: () => t`Build Items`,
|
||||
api_endpoint: ApiEndpoints.build_item_list,
|
||||
icon: 'build_order'
|
||||
},
|
||||
company: {
|
||||
label: () => t`Company`,
|
||||
label_multiple: () => t`Companies`,
|
||||
url_detail: '/company/:pk/',
|
||||
api_endpoint: ApiEndpoints.company_list,
|
||||
admin_url: '/company/company/',
|
||||
icon: 'building'
|
||||
},
|
||||
projectcode: {
|
||||
label: () => t`Project Code`,
|
||||
label_multiple: () => t`Project Codes`,
|
||||
url_detail: '/project-code/:pk/',
|
||||
api_endpoint: ApiEndpoints.project_code_list,
|
||||
icon: 'list_details'
|
||||
},
|
||||
purchaseorder: {
|
||||
label: () => t`Purchase Order`,
|
||||
label_multiple: () => t`Purchase Orders`,
|
||||
url_overview: '/purchasing/index/purchaseorders',
|
||||
url_detail: '/purchasing/purchase-order/:pk/',
|
||||
api_endpoint: ApiEndpoints.purchase_order_list,
|
||||
admin_url: '/order/purchaseorder/',
|
||||
icon: 'purchase_orders'
|
||||
},
|
||||
purchaseorderlineitem: {
|
||||
label: () => t`Purchase Order Line`,
|
||||
label_multiple: () => t`Purchase Order Lines`,
|
||||
api_endpoint: ApiEndpoints.purchase_order_line_list,
|
||||
icon: 'purchase_orders'
|
||||
},
|
||||
salesorder: {
|
||||
label: () => t`Sales Order`,
|
||||
label_multiple: () => t`Sales Orders`,
|
||||
url_overview: '/sales/index/salesorders',
|
||||
url_detail: '/sales/sales-order/:pk/',
|
||||
api_endpoint: ApiEndpoints.sales_order_list,
|
||||
admin_url: '/order/salesorder/',
|
||||
icon: 'sales_orders'
|
||||
},
|
||||
salesordershipment: {
|
||||
label: () => t`Sales Order Shipment`,
|
||||
label_multiple: () => t`Sales Order Shipments`,
|
||||
url_detail: '/sales/shipment/:pk/',
|
||||
api_endpoint: ApiEndpoints.sales_order_shipment_list,
|
||||
icon: 'sales_orders'
|
||||
},
|
||||
returnorder: {
|
||||
label: () => t`Return Order`,
|
||||
label_multiple: () => t`Return Orders`,
|
||||
url_overview: '/sales/index/returnorders',
|
||||
url_detail: '/sales/return-order/:pk/',
|
||||
api_endpoint: ApiEndpoints.return_order_list,
|
||||
admin_url: '/order/returnorder/',
|
||||
icon: 'return_orders'
|
||||
},
|
||||
returnorderlineitem: {
|
||||
label: () => t`Return Order Line Item`,
|
||||
label_multiple: () => t`Return Order Line Items`,
|
||||
api_endpoint: ApiEndpoints.return_order_line_list,
|
||||
icon: 'return_orders'
|
||||
},
|
||||
address: {
|
||||
label: () => t`Address`,
|
||||
label_multiple: () => t`Addresses`,
|
||||
url_detail: '/address/:pk/',
|
||||
api_endpoint: ApiEndpoints.address_list,
|
||||
icon: 'address'
|
||||
},
|
||||
contact: {
|
||||
label: () => t`Contact`,
|
||||
label_multiple: () => t`Contacts`,
|
||||
url_detail: '/contact/:pk/',
|
||||
api_endpoint: ApiEndpoints.contact_list,
|
||||
icon: 'group'
|
||||
},
|
||||
owner: {
|
||||
label: () => t`Owner`,
|
||||
label_multiple: () => t`Owners`,
|
||||
url_detail: '/owner/:pk/',
|
||||
api_endpoint: ApiEndpoints.owner_list,
|
||||
icon: 'group'
|
||||
},
|
||||
user: {
|
||||
label: () => t`User`,
|
||||
label_multiple: () => t`Users`,
|
||||
url_detail: '/core/user/:pk/',
|
||||
api_endpoint: ApiEndpoints.user_list,
|
||||
icon: 'user'
|
||||
},
|
||||
group: {
|
||||
label: () => t`Group`,
|
||||
label_multiple: () => t`Groups`,
|
||||
url_detail: '/core/group/:pk/',
|
||||
api_endpoint: ApiEndpoints.group_list,
|
||||
admin_url: '/auth/group/',
|
||||
icon: 'group'
|
||||
},
|
||||
importsession: {
|
||||
label: () => t`Import Session`,
|
||||
label_multiple: () => t`Import Sessions`,
|
||||
url_overview: '/settings/admin/import',
|
||||
url_detail: '/import/:pk/',
|
||||
api_endpoint: ApiEndpoints.import_session_list,
|
||||
icon: 'import'
|
||||
},
|
||||
labeltemplate: {
|
||||
label: () => t`Label Template`,
|
||||
label_multiple: () => t`Label Templates`,
|
||||
url_overview: '/settings/admin/labels',
|
||||
url_detail: '/settings/admin/labels/:pk/',
|
||||
api_endpoint: ApiEndpoints.label_list,
|
||||
icon: 'labels'
|
||||
},
|
||||
reporttemplate: {
|
||||
label: () => t`Report Template`,
|
||||
label_multiple: () => t`Report Templates`,
|
||||
url_overview: '/settings/admin/reports',
|
||||
url_detail: '/settings/admin/reports/:pk/',
|
||||
api_endpoint: ApiEndpoints.report_list,
|
||||
icon: 'reports'
|
||||
},
|
||||
pluginconfig: {
|
||||
label: () => t`Plugin Configuration`,
|
||||
label_multiple: () => t`Plugin Configurations`,
|
||||
url_overview: '/settings/admin/plugin',
|
||||
url_detail: '/settings/admin/plugin/:pk/',
|
||||
api_endpoint: ApiEndpoints.plugin_list,
|
||||
icon: 'plugin'
|
||||
},
|
||||
contenttype: {
|
||||
label: () => t`Content Type`,
|
||||
label_multiple: () => t`Content Types`,
|
||||
api_endpoint: ApiEndpoints.content_type_list,
|
||||
icon: 'list_details'
|
||||
},
|
||||
selectionlist: {
|
||||
label: () => t`Selection List`,
|
||||
label_multiple: () => t`Selection Lists`,
|
||||
api_endpoint: ApiEndpoints.selectionlist_list,
|
||||
icon: 'list_details'
|
||||
},
|
||||
error: {
|
||||
label: () => t`Error`,
|
||||
label_multiple: () => t`Errors`,
|
||||
api_endpoint: ApiEndpoints.error_report_list,
|
||||
url_overview: '/settings/admin/errors',
|
||||
url_detail: '/settings/admin/errors/:pk/',
|
||||
icon: 'exclamation'
|
||||
}
|
||||
};
|
||||
import {
|
||||
ModelInformationDict,
|
||||
type ModelInformationInterface
|
||||
} from '@lib/enums/ModelInformation';
|
||||
import type { ModelType } from '@lib/enums/ModelType';
|
||||
|
||||
/*
|
||||
* Extract model definition given the provided type - returns translatable strings for labels as string, not functions
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { t } from '@lingui/core/macro';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
import { ModelType } from '../../enums/ModelType';
|
||||
import { getDetailUrl } from '../../functions/urls';
|
||||
import { ModelType } from '@lib/enums/ModelType';
|
||||
import { getDetailUrl } from '@lib/functions/Navigation';
|
||||
import { type InstanceRenderInterface, RenderInlineModel } from './Instance';
|
||||
import { StatusRenderer } from './StatusRenderer';
|
||||
|
||||
|
@ -2,8 +2,8 @@ import { t } from '@lingui/core/macro';
|
||||
import { Badge } from '@mantine/core';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
import { ModelType } from '../../enums/ModelType';
|
||||
import { getDetailUrl } from '../../functions/urls';
|
||||
import { ModelType } from '@lib/enums/ModelType';
|
||||
import { getDetailUrl } from '@lib/functions/Navigation';
|
||||
import { ApiIcon } from '../items/ApiIcon';
|
||||
import { type InstanceRenderInterface, RenderInlineModel } from './Instance';
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Badge, Center, type MantineSize } from '@mantine/core';
|
||||
|
||||
import type { ModelType } from '@lib/enums/ModelType';
|
||||
import { statusColorMap } from '../../defaults/backendMappings';
|
||||
import type { ModelType } from '../../enums/ModelType';
|
||||
import { resolveItem } from '../../functions/conversion';
|
||||
import { useGlobalStatusState } from '../../states/StatusState';
|
||||
|
||||
|
@ -2,8 +2,8 @@ import { t } from '@lingui/core/macro';
|
||||
import { Text } from '@mantine/core';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
import { ModelType } from '../../enums/ModelType';
|
||||
import { getDetailUrl } from '../../functions/urls';
|
||||
import { ModelType } from '@lib/enums/ModelType';
|
||||
import { getDetailUrl } from '@lib/functions/Navigation';
|
||||
import { ApiIcon } from '../items/ApiIcon';
|
||||
import { type InstanceRenderInterface, RenderInlineModel } from './Instance';
|
||||
|
||||
|
@ -11,14 +11,14 @@ import {
|
||||
import { IconEdit } from '@tabler/icons-react';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { ModelInformationDict } from '@lib/enums/ModelInformation';
|
||||
import { ModelType } from '@lib/enums/ModelType';
|
||||
import { apiUrl } from '@lib/functions/Api';
|
||||
import type { Setting } from '@lib/types/Settings';
|
||||
import { api } from '../../App';
|
||||
import { ModelType } from '../../enums/ModelType';
|
||||
import { apiUrl } from '../../states/ApiState';
|
||||
import type { Setting } from '../../states/states';
|
||||
import { vars } from '../../theme';
|
||||
import { Boundary } from '../Boundary';
|
||||
import { RenderInstance } from '../render/Instance';
|
||||
import { ModelInformationDict } from '../render/ModelType';
|
||||
|
||||
/**
|
||||
* Render a single setting value
|
||||
|
@ -11,18 +11,17 @@ import React, {
|
||||
} from 'react';
|
||||
import { useStore } from 'zustand';
|
||||
|
||||
import type { ModelType } from '@lib/enums/ModelType';
|
||||
import { apiUrl } from '@lib/functions/Api';
|
||||
import type { Setting, SettingsStateProps } from '@lib/types/Settings';
|
||||
import { useApi } from '../../contexts/ApiContext';
|
||||
import type { ModelType } from '../../enums/ModelType';
|
||||
import { useEditApiFormModal } from '../../hooks/UseForm';
|
||||
import { apiUrl } from '../../states/ApiState';
|
||||
import {
|
||||
type SettingsStateProps,
|
||||
createMachineSettingsState,
|
||||
createPluginSettingsState,
|
||||
useGlobalSettingsState,
|
||||
useUserSettingsState
|
||||
} from '../../states/SettingsState';
|
||||
import type { Setting } from '../../states/states';
|
||||
import { SettingItem } from './SettingItem';
|
||||
|
||||
/**
|
||||
|
@ -1,22 +1,22 @@
|
||||
import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
|
||||
import { ModelType } from '@lib/enums/ModelType';
|
||||
import { apiUrl } from '@lib/functions/Api';
|
||||
import type { ApiFormFieldSet } from '@lib/types/Forms';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { Alert, Group, Paper, Tooltip } from '@mantine/core';
|
||||
import { showNotification } from '@mantine/notifications';
|
||||
import { IconShoppingCart } from '@tabler/icons-react';
|
||||
import { DataTable } from 'mantine-datatable';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||
import { ModelType } from '../../enums/ModelType';
|
||||
import { useSupplierPartFields } from '../../forms/CompanyForms';
|
||||
import { usePurchaseOrderFields } from '../../forms/PurchaseOrderForms';
|
||||
import { useCreateApiFormModal } from '../../hooks/UseForm';
|
||||
import useWizard from '../../hooks/UseWizard';
|
||||
import { apiUrl } from '../../states/ApiState';
|
||||
import { PartColumn } from '../../tables/ColumnRenderers';
|
||||
import { ActionButton } from '../buttons/ActionButton';
|
||||
import { AddItemButton } from '../buttons/AddItemButton';
|
||||
import RemoveRowButton from '../buttons/RemoveRowButton';
|
||||
import { StandaloneField } from '../forms/StandaloneField';
|
||||
import type { ApiFormFieldSet } from '../forms/fields/ApiFormField';
|
||||
import Expand from '../items/Expand';
|
||||
|
||||
/**
|
||||
|
@ -15,14 +15,14 @@ import { colorSchema } from './colorSchema';
|
||||
export function ThemeContext({
|
||||
children
|
||||
}: Readonly<{ children: JSX.Element }>) {
|
||||
const [usertheme] = useLocalState((state) => [state.usertheme]);
|
||||
const [userTheme] = useLocalState((state) => [state.userTheme]);
|
||||
|
||||
// Theme
|
||||
const myTheme = createTheme({
|
||||
primaryColor: usertheme.primaryColor,
|
||||
white: usertheme.whiteColor,
|
||||
black: usertheme.blackColor,
|
||||
defaultRadius: usertheme.radius,
|
||||
primaryColor: userTheme.primaryColor,
|
||||
white: userTheme.whiteColor,
|
||||
black: userTheme.blackColor,
|
||||
defaultRadius: userTheme.radius,
|
||||
breakpoints: {
|
||||
xs: '30em',
|
||||
sm: '48em',
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { ModelType } from '../enums/ModelType';
|
||||
import { ModelType } from '@lib/enums/ModelType';
|
||||
|
||||
/* Lookup tables for mapping backend responses to internal types */
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import type { HostList } from '../states/states';
|
||||
import type { HostList } from '@lib/types/Server';
|
||||
|
||||
export const defaultHostList: HostList = window.INVENTREE_SETTINGS.server_list;
|
||||
export const defaultHostKey = window.INVENTREE_SETTINGS.default_server;
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user