2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-06-16 12:05:53 +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:
Oliver
2025-04-16 00:30:34 +10:00
committed by GitHub
parent f3d804d5ea
commit 5e7e258289
276 changed files with 2797 additions and 1854 deletions

View File

@ -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
View 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
View 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
View 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
View 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).

View 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'
}
};

View 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;
}

View 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);
}
};

View 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
View 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';

View 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;
};

View 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>;

View 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;
};

View 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[];
}

View 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;
};

View 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;
}

View 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__;

View File

@ -0,0 +1,8 @@
export interface Host {
host: string;
name: string;
}
export interface HostList {
[key: string]: Host;
}

View 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
}

View 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;
};

View 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;
}

View File

@ -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"
}
}

View File

@ -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({

View File

@ -1,4 +1,4 @@
import type { ModelType } from '../../enums/ModelType';
import type { ModelType } from '@lib/enums/ModelType';
/**
* Interface defining a single barcode scan item

View File

@ -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';

View File

@ -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 = {

View File

@ -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;

View File

@ -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({

View File

@ -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 />;
}

View File

@ -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 {

View File

@ -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({

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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();

View File

@ -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>

View File

@ -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';

View File

@ -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.

View File

@ -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;

View File

@ -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';

View File

@ -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';

View File

@ -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

View File

@ -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';

View File

@ -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,

View File

@ -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>;

View File

@ -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
*/

View File

@ -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({

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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

View File

@ -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;

View File

@ -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';

View File

@ -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,

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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;

View File

@ -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;

View File

@ -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 };

View File

@ -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';

View File

@ -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 (

View File

@ -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;

View File

@ -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';

View File

@ -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,

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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

View File

@ -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 = {

View File

@ -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';

View File

@ -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';

View File

@ -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

View File

@ -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({

View File

@ -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,

View File

@ -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';

View File

@ -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'>

View File

@ -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 }}
/>
);
}

View File

@ -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'];

View File

@ -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>
);

View File

@ -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';

View File

@ -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';
/**

View File

@ -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,

View File

@ -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';
/**

View File

@ -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

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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

View File

@ -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';
/**

View File

@ -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';
/**

View File

@ -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',

View File

@ -1,4 +1,4 @@
import { ModelType } from '../enums/ModelType';
import { ModelType } from '@lib/enums/ModelType';
/* Lookup tables for mapping backend responses to internal types */

View File

@ -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