mirror of
https://github.com/inventree/InvenTree.git
synced 2025-04-28 03:26:45 +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:
parent
f3d804d5ea
commit
5e7e258289
1
.gitignore
vendored
1
.gitignore
vendored
@ -18,7 +18,6 @@ share/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
|
@ -18,7 +18,8 @@ If you add a lot of code (over ~1000 LOC) maybe split it into multiple plugins t
|
||||
Great. Now please read the [plugin documentation](./plugins.md) to get an overview of the architecture. It is rather short as a the (builtin) mixins come with extensive docstrings.
|
||||
|
||||
### Pick your building blocks
|
||||
Consider the usecase for your plugin and define the exact function of the plugin, maybe write it down in a short readme. Then pick the mixins you need (they help reduce custom code and keep the system reliable if internal calls change).
|
||||
|
||||
Consider the use-case for your plugin and define the exact function of the plugin, maybe write it down in a short readme. Then pick the mixins you need (they help reduce custom code and keep the system reliable if internal calls change).
|
||||
|
||||
- Is it just a simple REST-endpoint that runs a function ([ActionMixin](./plugins/action.md)) or a parser for a custom barcode format ([BarcodeMixin](./plugins/barcode.md))?
|
||||
- How does the user interact with the plugin? Is it a UI separate from the main InvenTree UI ([UrlsMixin](./plugins/urls.md)), does it need multiple pages with navigation-links ([NavigationMixin](./plugins/navigation.md)).
|
||||
@ -139,9 +140,7 @@ from plugin.mixins import ActionMixin
|
||||
|
||||
|
||||
class SampleActionPlugin(ActionMixin, InvenTreePlugin):
|
||||
"""
|
||||
Use docstrings for everything... pls
|
||||
"""
|
||||
"""Use docstrings for everything."""
|
||||
|
||||
NAME = "SampleActionPlugin"
|
||||
ACTION_NAME = "sample"
|
||||
|
@ -4,40 +4,8 @@ title: Third Party Integrations
|
||||
|
||||
## Third Party Integrations
|
||||
|
||||
A list of known third-party InvenTree extensions is provided below. If you have an extension that should be listed here, contact the InvenTree team on [GitHub](https://github.com/inventree/).
|
||||
A list of known third-party InvenTree extensions is provided [on our website](https://inventree.org/extend/integrate/) If you have an extension that should be listed here, contact the InvenTree team on [GitHub](https://github.com/inventree/).
|
||||
|
||||
### Ki-nTree
|
||||
## Available Plugins
|
||||
|
||||
[Ki-nTree](https://github.com/sparkmicro/Ki-nTree/) is a fantastic tool for automated creation of [KiCad](https://www.kicad.org/) library parts, with direct integration with InvenTree.
|
||||
|
||||
### PK2InvenTree
|
||||
|
||||
[PK2InvenTree](https://github.com/rgilham/PK2InvenTree) is an open-source tool for migrating an existing [PartKeepr](https://github.com/partkeepr/PartKeepr) database to InvenTree.
|
||||
|
||||
### Digikey-Inventree-Integration
|
||||
[Digikey-Inventree-Integration](https://github.com/EUdds/Digikey-Inventree-Integration) is a simple project that takes a digikey part number to creates a part in InvenTree.
|
||||
|
||||
### F360-InvenTree
|
||||
|
||||
[F360-InvenTree](https://github.com/matmair/F360-InvenTree/) is a tool for creating links between Autodesk Fusion 360 components and InvenTree parts.
|
||||
Still under heavy development.
|
||||
|
||||
### DigitalOcean droplet
|
||||
|
||||
[InvenTree droplet](https://inventree.org/digitalocean) is a 1-click solution to deploy InvenTree in the cloud with DigitalOcean. You still have to administer and update your instance.
|
||||
The source code for this droplet can be found in [inventree_droplet](https://github.com/invenhost/inventree_droplet).
|
||||
|
||||
### InvenTree zebra plugin
|
||||
|
||||
[InvenTree zebra plugin](https://github.com/SergeoLacruz/inventree-zebra-plugin) is a plugin to print labels with zebra printers.
|
||||
Currently only the GK420T printer is supported.
|
||||
|
||||
### InvenTree Apprise
|
||||
|
||||
[InvenTree Apprise](https://github.com/matmair/inventree-apprise) is a plugin to send notifications via Apprise. This enables a wide variety of targets.
|
||||
|
||||
## First party plugins
|
||||
|
||||
### InvenTree brother plugin
|
||||
|
||||
[InvenTree brother plugin](https://github.com/inventree/inventree-brother-plugin) is a plugin to print labels with brother Q series printers.
|
||||
Refer to the [InvenTree website](https://inventree.org/plugins.html) for a (non exhaustive) list of plugins that are available for InvenTree. This includes both official and third-party plugins.
|
||||
|
@ -4,7 +4,7 @@ title: Plugins
|
||||
|
||||
## InvenTree Plugin Architecture
|
||||
|
||||
The InvenTree server code supports an extensible plugin architecture, allowing custom plugins to be integrated directly into the database server. This allows development of complex behaviours which are decoupled from core InvenTree code.
|
||||
The InvenTree server code supports an extensible plugin architecture, allowing custom plugins to be integrated directly into the database server. This allows development of complex behaviors which are decoupled from core InvenTree code.
|
||||
|
||||
Plugins can be added from multiple sources:
|
||||
|
||||
@ -35,8 +35,8 @@ create-inventree-plugin
|
||||
|
||||
Custom plugins must inherit from the [InvenTreePlugin class]({{ sourcefile("src/backend/InvenTree/plugin/plugin.py") }}). Any plugins installed via the methods outlined above will be "discovered" when the InvenTree server launches.
|
||||
|
||||
!!! warning "Namechange"
|
||||
The name of the base class was changed with `0.7.0` from `IntegrationPluginBase` to `InvenTreePlugin`. While the old name is still available till `0.8.0` we strongly suggest upgrading your plugins. Deprecation warnings are raised if the old name is used.
|
||||
!!! warning "Name Change"
|
||||
The name of the base class was changed with `0.7.0` from `IntegrationPluginBase` to `InvenTreePlugin`.
|
||||
|
||||
### Imports
|
||||
|
||||
@ -66,7 +66,7 @@ Mixins are split up internally to keep the source tree clean and enable better t
|
||||
#### Models and other internal InvenTree APIs
|
||||
|
||||
!!! warning "Danger Zone"
|
||||
The APIs outside of the `plugin` namespace are not structured for public usage and require a more in-depth knowledge of the Django framework. Please ask in GitHub discussions of the `ÌnvenTree` org if you are not sure you are using something the intended way.
|
||||
The APIs outside of the `plugin` namespace are not structured for public usage and require a more in-depth knowledge of the Django framework. Please ask in GitHub discussions of the `InvenTree` org if you are not sure you are using something the intended way.
|
||||
|
||||
We do not provide stable interfaces to models or any other internal python APIs. If you need to integrate into these parts please make yourself familiar with the codebase. We follow general Django patterns and only stray from them in limited, special cases.
|
||||
If you need to react to state changes please use the [EventMixin](./plugins/event.md).
|
||||
@ -121,6 +121,7 @@ Supported mixin classes are:
|
||||
| [ReportMixin](./plugins/report.md) | Add custom context data to reports |
|
||||
| [ScheduleMixin](./plugins/schedule.md) | Schedule periodic tasks |
|
||||
| [SettingsMixin](./plugins/settings.md) | Integrate user configurable settings |
|
||||
| [UserInterfaceMixin](./plugins/ui.md) | Add custom user interface features |
|
||||
| [UrlsMixin](./plugins/urls.md) | Respond to custom URL endpoints |
|
||||
| [ValidationMixin](./plugins/validation.md) | Provide custom validation of database models |
|
||||
|
||||
|
@ -7,4 +7,4 @@ title: App Mixin
|
||||
If this mixin is added to a plugin the directory the plugin class is defined in is added to the list of `INSTALLED_APPS` in the InvenTree server configuration.
|
||||
|
||||
!!! warning "Danger Zone"
|
||||
Only use this mixin if you have an understanding of djangos [app system]({% include "django.html" %}/ref/applications). Plugins with this mixin are deeply integrated into InvenTree and can cause difficult to reproduce or long-running errors. Use the built-in testing functions of django to make sure your code does not cause unwanted behaviour in InvenTree before releasing.
|
||||
Only use this mixin if you have an understanding of Django's [app system]({% include "django.html" %}/ref/applications). Plugins with this mixin are deeply integrated into InvenTree and can cause difficult to reproduce or long-running errors. Use the built-in testing functions of Django to make sure your code does not cause unwanted behaviour in InvenTree before releasing.
|
||||
|
@ -5,7 +5,7 @@ title: Navigation Mixin
|
||||
## NavigationMixin
|
||||
|
||||
Use the class constant `NAVIGATION` for a array of links that should be added to InvenTrees navigation header.
|
||||
The array must contain at least one dict that at least define a name and a link for each element. The link must be formatted for a URL pattern name lookup - links to external sites are not possible directly. The optional icon must be a class reference to an icon (InvenTree ships with fontawesome 4 by default).
|
||||
The array must contain at least one dict that at least define a name and a link for each element. The link must be formatted for a URL pattern name lookup - links to external sites are not possible directly. The optional icon must be a class reference to an icon.
|
||||
|
||||
``` python
|
||||
class MyNavigationPlugin(NavigationMixin, InvenTreePlugin):
|
||||
|
@ -217,3 +217,47 @@ We are working to develop and distribute a library of custom InvenTree component
|
||||
### Examples
|
||||
|
||||
Refer to some of the existing InvenTree plugins linked above for examples of building custom UI plugins using the Mantine component library for seamless integration.
|
||||
|
||||
## Building a User Interface Plugin
|
||||
|
||||
The technology stack which allows custom plugins to hook into the InvenTree user interface utilizes the following components:
|
||||
|
||||
- [React](https://react.dev)
|
||||
- [Mantine](https://mantine.dev)
|
||||
- [TypeScript](https://www.typescriptlang.org/)
|
||||
- [Vite](https://vitejs.dev/)
|
||||
|
||||
While you don't need to be an expert in all of these technologies, it is recommended that you have a basic understanding of how they work together to build the InvenTree user interface. To get started, you should familiarize yourself with the frontend code (at `./src/frontend/`) as well as the vite configuration for the [InvenTree plugin creator](httsps://github.com/inventree/plugin-creator).
|
||||
|
||||
### Bundled with InvenTree
|
||||
|
||||
If a plugin is bundled with a separate copy of React libraries, issues may arise either due to version mismatches or because the React context is not shared between the InvenTree core and the plugin. This can lead to issues with rendering components, as the React context may not be shared between the two libraries.
|
||||
|
||||
To avoid issues, the InvenTree UI provides globally accessible components, which can be used as external modules by the plugin. This allows the plugin to use the same React context as the InvenTree core, and ensures that the plugin is compatible with the InvenTree user interface.
|
||||
|
||||
The following modules are provided as global objects at runtime:
|
||||
|
||||
- `React`
|
||||
- `ReactDOM`
|
||||
- `ReactDOMClient`
|
||||
|
||||
Additionally, for the Mantine library, the following modules are provided as global objects at runtime:
|
||||
|
||||
- `@mantine/core`
|
||||
- `@mantine/hooks`
|
||||
- `@mantine/notifications`
|
||||
|
||||
To use these modules in your plugin, they must be correctly *externalized* in the Vite configuration. Getting this right is crucial to ensure that the plugin is compatible with the InvenTree user interface. The [InvenTree plugin creator](https://github.com/inventree/plugin-creator) provides a good starting point for this configuration, and can be used to generate a new plugin with the correct configuration.
|
||||
|
||||
!!! info "Bundled Version"
|
||||
Keep in mind that the version of React and Mantine used in the InvenTree core may differ from the version used in your plugin. It is recommended to use the same version as the InvenTree core to avoid compatibility issues.
|
||||
|
||||
### Plugin Creator
|
||||
|
||||
The [InvenTree plugin creator](https://github.com/inventree/plugin-creator) provides an out-of-the-box setup for creating InvenTree plugins which integrate into the user interface. This includes a pre-configured Vite setup, which allows you to quickly get started with building your own custom UI plugins.
|
||||
|
||||
Using the plugin creator tool is the recommended way to get started with building custom UI plugins for InvenTree, as it provides a solid foundation to build upon. It is also the only method which is officially supported by the InvenTree development team!
|
||||
|
||||
### DIY
|
||||
|
||||
Of course, you can also build your own custom UI plugins from scratch. This is a more advanced option, and requires a good understanding of the InvenTree codebase, as well as the technologies used to build the user interface. You are free to use other web technologies, however if you choose to do this, don't expect any support from the InvenTree development team. We will only provide support for plugins which are built using the recommended stack, and which follow the guidelines outlined in this documentation.
|
||||
|
@ -69,7 +69,7 @@ from plugin.mixins import ValidationMixin
|
||||
import part.models
|
||||
|
||||
|
||||
class MyValidationMixin(Validationixin, InvenTreePlugin):
|
||||
class MyValidationMixin(ValidationMixin, InvenTreePlugin):
|
||||
"""Custom validation plugin."""
|
||||
|
||||
def validate_model_instance(self, instance, deltas=None):
|
||||
|
@ -41,8 +41,8 @@
|
||||
"pseudo-LOCALE"],
|
||||
"catalogs": [{
|
||||
"path": "src/locales/{locale}/messages",
|
||||
"include": ["src"],
|
||||
"exclude": ["**/node_modules/**"]
|
||||
"include": ["src", "lib"],
|
||||
"exclude": ["**/node_modules/**", "./dist/**"]
|
||||
}],
|
||||
"format": "po",
|
||||
"orderBy": "origin",
|
||||
|
14
src/frontend/.npmignore
Normal file
14
src/frontend/.npmignore
Normal file
@ -0,0 +1,14 @@
|
||||
# Testing code
|
||||
playwright.config.ts
|
||||
playwright/
|
||||
tests/
|
||||
test-results/
|
||||
|
||||
# Source files (not part of public API)
|
||||
src/
|
||||
|
||||
# Build output
|
||||
node_modules/
|
||||
|
||||
# Other files
|
||||
.gitignore
|
15
src/frontend/CHANGELOG.md
Normal file
15
src/frontend/CHANGELOG.md
Normal file
@ -0,0 +1,15 @@
|
||||
## InvenTree UI Components - Changelog
|
||||
|
||||
This file contains historical changelog information for the InvenTree UI components library.
|
||||
|
||||
### 1.0.0 - April 2025
|
||||
|
||||
Published the first version of the UI components API. This allows external plugins to hook into the InvenTree user interface, and provides global access to the following objects:
|
||||
|
||||
- `window.React`: The core `react` library running on the UI
|
||||
- `window.ReactDOM`: The `react-dom` library
|
||||
- `window.ReactDOMClient`: The `react-dom/client` library
|
||||
- `window.MantineCore`: The `@mantine/core` library
|
||||
- `window.MantineNotifications`: The `@mantine/notifications` library
|
||||
|
||||
All of these components can be "externalized" in the plugin build step.
|
21
src/frontend/LICENSE
Normal file
21
src/frontend/LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017 - InvenTree Developers
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
28
src/frontend/README.md
Normal file
28
src/frontend/README.md
Normal file
@ -0,0 +1,28 @@
|
||||
## inventree-ui
|
||||
|
||||
User Interface (UI) elements for the [InvenTree](https://inventree.org) web interface.
|
||||
|
||||
|
||||
### Description
|
||||
|
||||
This package provides a public interface allowing plugins to hook into core UI functionality. In particular, it defines a set of interface types provided by the InvenTree user interface, to be used by a custom plugin to implement some custom UI feature.
|
||||
|
||||
This library is intended to be used for creating plugins - any other use is outside of its scope and is not supported.
|
||||
|
||||
### Plugin Creator
|
||||
|
||||
This library is intended to be used with the [InvenTree Plugin Creator](https://github.com/inventree/plugin-creator). Read the documentation for the plugin creation tool for more information.
|
||||
|
||||
The plugin creation tool uses the types provided in this package at build time, but it is intended that most of the major packages are *externalized* - as these are provided as global objects by the core InvenTree UI code.
|
||||
|
||||
### Installation
|
||||
|
||||
This should be installed as a part of the plugin creator tool. If you need to install it manually, e.g. using `npm`:
|
||||
|
||||
```
|
||||
npm i @inventreedb/ui
|
||||
```
|
||||
|
||||
### Versioning
|
||||
|
||||
Each change to the plugin API will be described in the [CHANGELOG file](./CHANGELOG.md).
|
279
src/frontend/lib/enums/ModelInformation.tsx
Normal file
279
src/frontend/lib/enums/ModelInformation.tsx
Normal file
@ -0,0 +1,279 @@
|
||||
import { t } from '@lingui/core/macro';
|
||||
import type { InvenTreeIconType } from '../types/Icons';
|
||||
import { ApiEndpoints } from './ApiEndpoints';
|
||||
import type { ModelType } from './ModelType';
|
||||
|
||||
export interface ModelInformationInterface {
|
||||
label: string;
|
||||
label_multiple: string;
|
||||
url_overview?: string;
|
||||
url_detail?: string;
|
||||
api_endpoint: ApiEndpoints;
|
||||
admin_url?: string;
|
||||
icon: keyof InvenTreeIconType;
|
||||
}
|
||||
|
||||
export interface TranslatableModelInformationInterface
|
||||
extends Omit<ModelInformationInterface, 'label' | 'label_multiple'> {
|
||||
label: () => string;
|
||||
label_multiple: () => string;
|
||||
}
|
||||
|
||||
export type ModelDict = {
|
||||
[key in keyof typeof ModelType]: TranslatableModelInformationInterface;
|
||||
};
|
||||
|
||||
export const ModelInformationDict: ModelDict = {
|
||||
part: {
|
||||
label: () => t`Part`,
|
||||
label_multiple: () => t`Parts`,
|
||||
url_overview: '/part/category/index/parts',
|
||||
url_detail: '/part/:pk/',
|
||||
api_endpoint: ApiEndpoints.part_list,
|
||||
admin_url: '/part/part/',
|
||||
icon: 'part'
|
||||
},
|
||||
partparametertemplate: {
|
||||
label: () => t`Part Parameter Template`,
|
||||
label_multiple: () => t`Part Parameter Templates`,
|
||||
url_detail: '/partparametertemplate/:pk/',
|
||||
api_endpoint: ApiEndpoints.part_parameter_template_list,
|
||||
icon: 'test_templates'
|
||||
},
|
||||
parttesttemplate: {
|
||||
label: () => t`Part Test Template`,
|
||||
label_multiple: () => t`Part Test Templates`,
|
||||
url_detail: '/parttesttemplate/:pk/',
|
||||
api_endpoint: ApiEndpoints.part_test_template_list,
|
||||
icon: 'test'
|
||||
},
|
||||
supplierpart: {
|
||||
label: () => t`Supplier Part`,
|
||||
label_multiple: () => t`Supplier Parts`,
|
||||
url_overview: '/purchasing/index/supplier-parts',
|
||||
url_detail: '/purchasing/supplier-part/:pk/',
|
||||
api_endpoint: ApiEndpoints.supplier_part_list,
|
||||
admin_url: '/company/supplierpart/',
|
||||
icon: 'supplier_part'
|
||||
},
|
||||
manufacturerpart: {
|
||||
label: () => t`Manufacturer Part`,
|
||||
label_multiple: () => t`Manufacturer Parts`,
|
||||
url_overview: '/purchasing/index/manufacturer-parts',
|
||||
url_detail: '/purchasing/manufacturer-part/:pk/',
|
||||
api_endpoint: ApiEndpoints.manufacturer_part_list,
|
||||
admin_url: '/company/manufacturerpart/',
|
||||
icon: 'manufacturers'
|
||||
},
|
||||
partcategory: {
|
||||
label: () => t`Part Category`,
|
||||
label_multiple: () => t`Part Categories`,
|
||||
url_overview: '/part/category/parts/subcategories',
|
||||
url_detail: '/part/category/:pk/',
|
||||
api_endpoint: ApiEndpoints.category_list,
|
||||
admin_url: '/part/partcategory/',
|
||||
icon: 'category'
|
||||
},
|
||||
stockitem: {
|
||||
label: () => t`Stock Item`,
|
||||
label_multiple: () => t`Stock Items`,
|
||||
url_overview: '/stock/location/index/stock-items',
|
||||
url_detail: '/stock/item/:pk/',
|
||||
api_endpoint: ApiEndpoints.stock_item_list,
|
||||
admin_url: '/stock/stockitem/',
|
||||
icon: 'stock'
|
||||
},
|
||||
stocklocation: {
|
||||
label: () => t`Stock Location`,
|
||||
label_multiple: () => t`Stock Locations`,
|
||||
url_overview: '/stock/location',
|
||||
url_detail: '/stock/location/:pk/',
|
||||
api_endpoint: ApiEndpoints.stock_location_list,
|
||||
admin_url: '/stock/stocklocation/',
|
||||
icon: 'location'
|
||||
},
|
||||
stocklocationtype: {
|
||||
label: () => t`Stock Location Type`,
|
||||
label_multiple: () => t`Stock Location Types`,
|
||||
api_endpoint: ApiEndpoints.stock_location_type_list,
|
||||
icon: 'location'
|
||||
},
|
||||
stockhistory: {
|
||||
label: () => t`Stock History`,
|
||||
label_multiple: () => t`Stock Histories`,
|
||||
api_endpoint: ApiEndpoints.stock_tracking_list,
|
||||
icon: 'history'
|
||||
},
|
||||
build: {
|
||||
label: () => t`Build`,
|
||||
label_multiple: () => t`Builds`,
|
||||
url_overview: '/manufacturing/index/buildorders/',
|
||||
url_detail: '/manufacturing/build-order/:pk/',
|
||||
api_endpoint: ApiEndpoints.build_order_list,
|
||||
admin_url: '/build/build/',
|
||||
icon: 'build_order'
|
||||
},
|
||||
buildline: {
|
||||
label: () => t`Build Line`,
|
||||
label_multiple: () => t`Build Lines`,
|
||||
url_overview: '/build/line',
|
||||
url_detail: '/build/line/:pk/',
|
||||
api_endpoint: ApiEndpoints.build_line_list,
|
||||
icon: 'build_order'
|
||||
},
|
||||
builditem: {
|
||||
label: () => t`Build Item`,
|
||||
label_multiple: () => t`Build Items`,
|
||||
api_endpoint: ApiEndpoints.build_item_list,
|
||||
icon: 'build_order'
|
||||
},
|
||||
company: {
|
||||
label: () => t`Company`,
|
||||
label_multiple: () => t`Companies`,
|
||||
url_detail: '/company/:pk/',
|
||||
api_endpoint: ApiEndpoints.company_list,
|
||||
admin_url: '/company/company/',
|
||||
icon: 'building'
|
||||
},
|
||||
projectcode: {
|
||||
label: () => t`Project Code`,
|
||||
label_multiple: () => t`Project Codes`,
|
||||
url_detail: '/project-code/:pk/',
|
||||
api_endpoint: ApiEndpoints.project_code_list,
|
||||
icon: 'list_details'
|
||||
},
|
||||
purchaseorder: {
|
||||
label: () => t`Purchase Order`,
|
||||
label_multiple: () => t`Purchase Orders`,
|
||||
url_overview: '/purchasing/index/purchaseorders',
|
||||
url_detail: '/purchasing/purchase-order/:pk/',
|
||||
api_endpoint: ApiEndpoints.purchase_order_list,
|
||||
admin_url: '/order/purchaseorder/',
|
||||
icon: 'purchase_orders'
|
||||
},
|
||||
purchaseorderlineitem: {
|
||||
label: () => t`Purchase Order Line`,
|
||||
label_multiple: () => t`Purchase Order Lines`,
|
||||
api_endpoint: ApiEndpoints.purchase_order_line_list,
|
||||
icon: 'purchase_orders'
|
||||
},
|
||||
salesorder: {
|
||||
label: () => t`Sales Order`,
|
||||
label_multiple: () => t`Sales Orders`,
|
||||
url_overview: '/sales/index/salesorders',
|
||||
url_detail: '/sales/sales-order/:pk/',
|
||||
api_endpoint: ApiEndpoints.sales_order_list,
|
||||
admin_url: '/order/salesorder/',
|
||||
icon: 'sales_orders'
|
||||
},
|
||||
salesordershipment: {
|
||||
label: () => t`Sales Order Shipment`,
|
||||
label_multiple: () => t`Sales Order Shipments`,
|
||||
url_detail: '/sales/shipment/:pk/',
|
||||
api_endpoint: ApiEndpoints.sales_order_shipment_list,
|
||||
icon: 'sales_orders'
|
||||
},
|
||||
returnorder: {
|
||||
label: () => t`Return Order`,
|
||||
label_multiple: () => t`Return Orders`,
|
||||
url_overview: '/sales/index/returnorders',
|
||||
url_detail: '/sales/return-order/:pk/',
|
||||
api_endpoint: ApiEndpoints.return_order_list,
|
||||
admin_url: '/order/returnorder/',
|
||||
icon: 'return_orders'
|
||||
},
|
||||
returnorderlineitem: {
|
||||
label: () => t`Return Order Line Item`,
|
||||
label_multiple: () => t`Return Order Line Items`,
|
||||
api_endpoint: ApiEndpoints.return_order_line_list,
|
||||
icon: 'return_orders'
|
||||
},
|
||||
address: {
|
||||
label: () => t`Address`,
|
||||
label_multiple: () => t`Addresses`,
|
||||
url_detail: '/address/:pk/',
|
||||
api_endpoint: ApiEndpoints.address_list,
|
||||
icon: 'address'
|
||||
},
|
||||
contact: {
|
||||
label: () => t`Contact`,
|
||||
label_multiple: () => t`Contacts`,
|
||||
url_detail: '/contact/:pk/',
|
||||
api_endpoint: ApiEndpoints.contact_list,
|
||||
icon: 'group'
|
||||
},
|
||||
owner: {
|
||||
label: () => t`Owner`,
|
||||
label_multiple: () => t`Owners`,
|
||||
url_detail: '/owner/:pk/',
|
||||
api_endpoint: ApiEndpoints.owner_list,
|
||||
icon: 'group'
|
||||
},
|
||||
user: {
|
||||
label: () => t`User`,
|
||||
label_multiple: () => t`Users`,
|
||||
url_detail: '/core/user/:pk/',
|
||||
api_endpoint: ApiEndpoints.user_list,
|
||||
icon: 'user'
|
||||
},
|
||||
group: {
|
||||
label: () => t`Group`,
|
||||
label_multiple: () => t`Groups`,
|
||||
url_detail: '/core/group/:pk/',
|
||||
api_endpoint: ApiEndpoints.group_list,
|
||||
admin_url: '/auth/group/',
|
||||
icon: 'group'
|
||||
},
|
||||
importsession: {
|
||||
label: () => t`Import Session`,
|
||||
label_multiple: () => t`Import Sessions`,
|
||||
url_overview: '/settings/admin/import',
|
||||
url_detail: '/import/:pk/',
|
||||
api_endpoint: ApiEndpoints.import_session_list,
|
||||
icon: 'import'
|
||||
},
|
||||
labeltemplate: {
|
||||
label: () => t`Label Template`,
|
||||
label_multiple: () => t`Label Templates`,
|
||||
url_overview: '/settings/admin/labels',
|
||||
url_detail: '/settings/admin/labels/:pk/',
|
||||
api_endpoint: ApiEndpoints.label_list,
|
||||
icon: 'labels'
|
||||
},
|
||||
reporttemplate: {
|
||||
label: () => t`Report Template`,
|
||||
label_multiple: () => t`Report Templates`,
|
||||
url_overview: '/settings/admin/reports',
|
||||
url_detail: '/settings/admin/reports/:pk/',
|
||||
api_endpoint: ApiEndpoints.report_list,
|
||||
icon: 'reports'
|
||||
},
|
||||
pluginconfig: {
|
||||
label: () => t`Plugin Configuration`,
|
||||
label_multiple: () => t`Plugin Configurations`,
|
||||
url_overview: '/settings/admin/plugin',
|
||||
url_detail: '/settings/admin/plugin/:pk/',
|
||||
api_endpoint: ApiEndpoints.plugin_list,
|
||||
icon: 'plugin'
|
||||
},
|
||||
contenttype: {
|
||||
label: () => t`Content Type`,
|
||||
label_multiple: () => t`Content Types`,
|
||||
api_endpoint: ApiEndpoints.content_type_list,
|
||||
icon: 'list_details'
|
||||
},
|
||||
selectionlist: {
|
||||
label: () => t`Selection List`,
|
||||
label_multiple: () => t`Selection Lists`,
|
||||
api_endpoint: ApiEndpoints.selectionlist_list,
|
||||
icon: 'list_details'
|
||||
},
|
||||
error: {
|
||||
label: () => t`Error`,
|
||||
label_multiple: () => t`Errors`,
|
||||
api_endpoint: ApiEndpoints.error_report_list,
|
||||
url_overview: '/settings/admin/errors',
|
||||
url_detail: '/settings/admin/errors/:pk/',
|
||||
icon: 'exclamation'
|
||||
}
|
||||
};
|
42
src/frontend/lib/functions/Api.tsx
Normal file
42
src/frontend/lib/functions/Api.tsx
Normal file
@ -0,0 +1,42 @@
|
||||
import type { ApiEndpoints } from '../enums/ApiEndpoints';
|
||||
import type { PathParams } from '../types/Core';
|
||||
|
||||
/**
|
||||
* Function to return the API prefix.
|
||||
* For now it is fixed, but may be configurable in the future.
|
||||
*/
|
||||
export function apiPrefix(): string {
|
||||
return '/api/';
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an API URL with an endpoint and (optional) pk value
|
||||
*/
|
||||
export function apiUrl(
|
||||
endpoint: ApiEndpoints | string,
|
||||
pk?: any,
|
||||
pathParams?: PathParams
|
||||
): string {
|
||||
let _url = endpoint;
|
||||
|
||||
// If the URL does not start with a '/', add the API prefix
|
||||
if (!_url.startsWith('/')) {
|
||||
_url = apiPrefix() + _url;
|
||||
}
|
||||
|
||||
if (_url && pk) {
|
||||
if (_url.indexOf(':id') >= 0) {
|
||||
_url = _url.replace(':id', `${pk}`);
|
||||
} else {
|
||||
_url += `${pk}/`;
|
||||
}
|
||||
}
|
||||
|
||||
if (_url && pathParams) {
|
||||
for (const key in pathParams) {
|
||||
_url = _url.replace(`:${key}`, `${pathParams[key]}`);
|
||||
}
|
||||
}
|
||||
|
||||
return _url;
|
||||
}
|
70
src/frontend/lib/functions/Navigation.tsx
Normal file
70
src/frontend/lib/functions/Navigation.tsx
Normal file
@ -0,0 +1,70 @@
|
||||
import type { NavigateFunction } from 'react-router-dom';
|
||||
import { ModelInformationDict } from '../enums/ModelInformation';
|
||||
import type { ModelType } from '../enums/ModelType';
|
||||
import { cancelEvent } from './Events';
|
||||
|
||||
export const getBaseUrl = (): string =>
|
||||
(window as any).INVENTREE_SETTINGS?.base_url || 'web';
|
||||
|
||||
/**
|
||||
* Returns the detail view URL for a given model type
|
||||
*/
|
||||
export function getDetailUrl(
|
||||
model: ModelType,
|
||||
pk: number | string,
|
||||
absolute?: boolean
|
||||
): string {
|
||||
const modelInfo = ModelInformationDict[model];
|
||||
|
||||
if (pk === undefined || pk === null) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (!!pk && modelInfo && modelInfo.url_detail) {
|
||||
const url = modelInfo.url_detail.replace(':pk', pk.toString());
|
||||
const base = getBaseUrl();
|
||||
|
||||
if (absolute && base) {
|
||||
return `/${base}${url}`;
|
||||
} else {
|
||||
return url;
|
||||
}
|
||||
}
|
||||
|
||||
console.error(`No detail URL found for model ${model} <${pk}>`);
|
||||
return '';
|
||||
}
|
||||
|
||||
/*
|
||||
* Navigate to a provided link.
|
||||
* - If the link is to be opened externally, open it in a new tab.
|
||||
* - Otherwise, navigate using the provided navigate function.
|
||||
*/
|
||||
export const navigateToLink = (
|
||||
link: string,
|
||||
navigate: NavigateFunction,
|
||||
event: any
|
||||
) => {
|
||||
cancelEvent(event);
|
||||
|
||||
const base = `/${getBaseUrl()}`;
|
||||
|
||||
if (event?.ctrlKey || event?.shiftKey) {
|
||||
// Open the link in a new tab
|
||||
let url = link;
|
||||
if (link.startsWith('/') && !link.startsWith(base)) {
|
||||
url = `${base}${link}`;
|
||||
}
|
||||
window.open(url, '_blank');
|
||||
} else {
|
||||
// Navigate internally
|
||||
let url = link;
|
||||
|
||||
if (link.startsWith(base)) {
|
||||
// Strip the base URL from the link
|
||||
url = link.replace(base, '');
|
||||
}
|
||||
|
||||
navigate(url);
|
||||
}
|
||||
};
|
30
src/frontend/lib/functions/Plugins.tsx
Normal file
30
src/frontend/lib/functions/Plugins.tsx
Normal file
@ -0,0 +1,30 @@
|
||||
import {
|
||||
INVENTREE_PLUGIN_VERSION,
|
||||
type InvenTreePluginContext
|
||||
} from '../types/Plugins';
|
||||
|
||||
function extractVersion(version: string) {
|
||||
// Extract the version number from the string
|
||||
const [major, minor, patch] = version
|
||||
.split('.')
|
||||
.map((v) => Number.parseInt(v, 10));
|
||||
|
||||
return { major, minor, patch };
|
||||
}
|
||||
|
||||
/**
|
||||
* Check th e
|
||||
*/
|
||||
export function checkPluginVersion(context: InvenTreePluginContext) {
|
||||
const pluginVersion = extractVersion(INVENTREE_PLUGIN_VERSION);
|
||||
const systemVersion = extractVersion(context.version.inventree);
|
||||
|
||||
const mismatch = `Plugin version mismatch! Expected version ${INVENTREE_PLUGIN_VERSION}, got ${context.version}`;
|
||||
|
||||
// A major version mismatch indicates a potentially breaking change
|
||||
if (pluginVersion.major !== systemVersion.major) {
|
||||
console.warn(mismatch);
|
||||
} else if (INVENTREE_PLUGIN_VERSION != context.version.inventree) {
|
||||
console.info(mismatch);
|
||||
}
|
||||
}
|
23
src/frontend/lib/index.ts
Normal file
23
src/frontend/lib/index.ts
Normal file
@ -0,0 +1,23 @@
|
||||
// Constant value definitions
|
||||
export {
|
||||
INVENTREE_PLUGIN_VERSION,
|
||||
INVENTREE_REACT_VERSION,
|
||||
INVENTREE_REACT_DOM_VERSION,
|
||||
INVENTREE_MANTINE_VERSION
|
||||
} from './types/Plugins';
|
||||
|
||||
// Common type definitions
|
||||
export { ApiEndpoints } from './enums/ApiEndpoints';
|
||||
export { ModelType } from './enums/ModelType';
|
||||
export { UserRoles, UserPermissions } from './enums/Roles';
|
||||
|
||||
export type { InvenTreePluginContext } from './types/Plugins';
|
||||
|
||||
// Common utility functions
|
||||
export { apiUrl } from './functions/Api';
|
||||
export {
|
||||
getBaseUrl,
|
||||
getDetailUrl,
|
||||
navigateToLink
|
||||
} from './functions/Navigation';
|
||||
export { checkPluginVersion } from './functions/Plugins';
|
51
src/frontend/lib/types/Auth.tsx
Normal file
51
src/frontend/lib/types/Auth.tsx
Normal file
@ -0,0 +1,51 @@
|
||||
export interface AuthContext {
|
||||
status: number;
|
||||
data: { flows: Flow[] };
|
||||
meta: { is_authenticated: boolean };
|
||||
}
|
||||
|
||||
export enum FlowEnum {
|
||||
VerifyEmail = 'verify_email',
|
||||
Login = 'login',
|
||||
Signup = 'signup',
|
||||
ProviderRedirect = 'provider_redirect',
|
||||
ProviderSignup = 'provider_signup',
|
||||
ProviderToken = 'provider_token',
|
||||
MfaAuthenticate = 'mfa_authenticate',
|
||||
Reauthenticate = 'reauthenticate',
|
||||
MfaReauthenticate = 'mfa_reauthenticate'
|
||||
}
|
||||
|
||||
export interface Flow {
|
||||
id: FlowEnum;
|
||||
providers?: string[];
|
||||
is_pending?: boolean[];
|
||||
}
|
||||
|
||||
export interface AuthProvider {
|
||||
id: string;
|
||||
name: string;
|
||||
flows: string[];
|
||||
client_id: string;
|
||||
}
|
||||
|
||||
export interface AuthConfig {
|
||||
account: {
|
||||
authentication_method: string;
|
||||
};
|
||||
socialaccount: { providers: AuthProvider[] };
|
||||
mfa: {
|
||||
supported_types: string[];
|
||||
};
|
||||
usersessions: {
|
||||
track_activity: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
// Errors
|
||||
export type ErrorResponse = {
|
||||
data: any;
|
||||
status: number;
|
||||
statusText: string;
|
||||
message?: string;
|
||||
};
|
13
src/frontend/lib/types/Core.tsx
Normal file
13
src/frontend/lib/types/Core.tsx
Normal file
@ -0,0 +1,13 @@
|
||||
import type { MantineSize } from '@mantine/core';
|
||||
|
||||
export type UiSizeType = MantineSize | string | number;
|
||||
|
||||
export interface UserTheme {
|
||||
primaryColor: string;
|
||||
whiteColor: string;
|
||||
blackColor: string;
|
||||
radius: UiSizeType;
|
||||
loader: string;
|
||||
}
|
||||
|
||||
export type PathParams = Record<string, string | number>;
|
69
src/frontend/lib/types/Filters.tsx
Normal file
69
src/frontend/lib/types/Filters.tsx
Normal file
@ -0,0 +1,69 @@
|
||||
import type { ModelType } from '../enums/ModelType';
|
||||
|
||||
/**
|
||||
* Interface for the table filter choice
|
||||
*/
|
||||
export type TableFilterChoice = {
|
||||
value: string;
|
||||
label: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Available filter types
|
||||
*
|
||||
* boolean: A simple true/false filter
|
||||
* choice: A filter which allows selection from a list of (supplied)
|
||||
* date: A filter which allows selection from a date input
|
||||
* text: A filter which allows raw text input
|
||||
* api: A filter which fetches its options from an API endpoint
|
||||
*/
|
||||
export type TableFilterType = 'boolean' | 'choice' | 'date' | 'text' | 'api';
|
||||
|
||||
/**
|
||||
* Interface for the table filter type. Provides a number of options for selecting filter value:
|
||||
*
|
||||
* name: The name of the filter (used for query string)
|
||||
* label: The label to display in the UI (human readable)
|
||||
* description: A description of the filter (human readable)
|
||||
* type: The type of filter (see TableFilterType)
|
||||
* choices: A list of TableFilterChoice objects
|
||||
* choiceFunction: A function which returns a list of TableFilterChoice objects
|
||||
* defaultValue: The default value for the filter
|
||||
* value: The current value of the filter
|
||||
* displayValue: The current display value of the filter
|
||||
* active: Whether the filter is active (false = hidden, not used)
|
||||
* apiUrl: The API URL to use for fetching dynamic filter options
|
||||
* model: The model type to use for fetching dynamic filter options
|
||||
* modelRenderer: A function to render a simple text version of the model type
|
||||
*/
|
||||
export type TableFilter = {
|
||||
name: string;
|
||||
label: string;
|
||||
description?: string;
|
||||
type?: TableFilterType;
|
||||
choices?: TableFilterChoice[];
|
||||
choiceFunction?: () => TableFilterChoice[];
|
||||
defaultValue?: any;
|
||||
value?: any;
|
||||
displayValue?: any;
|
||||
active?: boolean;
|
||||
apiUrl?: string;
|
||||
model?: ModelType;
|
||||
modelRenderer?: (instance: any) => string;
|
||||
};
|
||||
|
||||
/*
|
||||
* Type definition for representing the state of a group of filters.
|
||||
* These may be applied to a data view (e.g. table, calendar) to filter the displayed data.
|
||||
*
|
||||
* filterKey: A unique key for the filter set
|
||||
* activeFilters: An array of active filters
|
||||
* setActiveFilters: A function to set the active filters
|
||||
* clearActiveFilters: A function to clear all active filters
|
||||
*/
|
||||
export type FilterSetState = {
|
||||
filterKey: string;
|
||||
activeFilters: TableFilter[];
|
||||
setActiveFilters: (filters: TableFilter[]) => void;
|
||||
clearActiveFilters: () => void;
|
||||
};
|
187
src/frontend/lib/types/Forms.tsx
Normal file
187
src/frontend/lib/types/Forms.tsx
Normal file
@ -0,0 +1,187 @@
|
||||
import type { DefaultMantineColor, MantineStyleProp } from '@mantine/core';
|
||||
import type { UseFormReturnType } from '@mantine/form';
|
||||
import type { ReactNode } from 'react';
|
||||
import type { FieldValues, UseFormReturn } from 'react-hook-form';
|
||||
import type { ApiEndpoints } from '../enums/ApiEndpoints';
|
||||
import type { ModelType } from '../enums/ModelType';
|
||||
import type { PathParams, UiSizeType } from './Core';
|
||||
import type { TableState } from './Tables';
|
||||
|
||||
export interface ApiFormAction {
|
||||
text: string;
|
||||
variant?: 'outline';
|
||||
color?: DefaultMantineColor;
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
export type ApiFormData = UseFormReturnType<Record<string, unknown>>;
|
||||
|
||||
export type ApiFormAdjustFilterType = {
|
||||
filters: any;
|
||||
data: FieldValues;
|
||||
};
|
||||
|
||||
export type ApiFormFieldChoice = {
|
||||
value: any;
|
||||
display_name: string;
|
||||
};
|
||||
|
||||
// Define individual headers in a table field
|
||||
export type ApiFormFieldHeader = {
|
||||
title: string;
|
||||
style?: MantineStyleProp;
|
||||
};
|
||||
|
||||
/** Definition of the ApiForm field component.
|
||||
* - The 'name' attribute *must* be provided
|
||||
* - All other attributes are optional, and may be provided by the API
|
||||
* - However, they can be overridden by the user
|
||||
*
|
||||
* @param name : The name of the field
|
||||
* @param label : The label to display for the field
|
||||
* @param value : The value of the field
|
||||
* @param default : The default value of the field
|
||||
* @param icon : An icon to display next to the field
|
||||
* @param field_type : The type of field to render
|
||||
* @param api_url : The API endpoint to fetch data from (for related fields)
|
||||
* @param pk_field : The primary key field for the related field (default = "pk")
|
||||
* @param model : The model to use for related fields
|
||||
* @param filters : Optional API filters to apply to related fields
|
||||
* @param required : Whether the field is required
|
||||
* @param hidden : Whether the field is hidden
|
||||
* @param disabled : Whether the field is disabled
|
||||
* @param error : Optional error message to display
|
||||
* @param exclude : Whether to exclude the field from the submitted data
|
||||
* @param placeholder : The placeholder text to display
|
||||
* @param description : The description to display for the field
|
||||
* @param preFieldContent : Content to render before the field
|
||||
* @param postFieldContent : Content to render after the field
|
||||
* @param onValueChange : Callback function to call when the field value changes
|
||||
* @param adjustFilters : Callback function to adjust the filters for a related field before a query is made
|
||||
* @param adjustValue : Callback function to adjust the value of the field before it is sent to the API
|
||||
* @param addRow : Callback function to add a new row to a table field
|
||||
* @param onKeyDown : Callback function to get which key was pressed in the form to handle submission on enter
|
||||
*/
|
||||
export type ApiFormFieldType = {
|
||||
label?: string;
|
||||
value?: any;
|
||||
default?: any;
|
||||
icon?: ReactNode;
|
||||
field_type?:
|
||||
| 'related field'
|
||||
| 'email'
|
||||
| 'url'
|
||||
| 'string'
|
||||
| 'icon'
|
||||
| 'boolean'
|
||||
| 'date'
|
||||
| 'datetime'
|
||||
| 'integer'
|
||||
| 'decimal'
|
||||
| 'float'
|
||||
| 'number'
|
||||
| 'choice'
|
||||
| 'file upload'
|
||||
| 'nested object'
|
||||
| 'dependent field'
|
||||
| 'table';
|
||||
api_url?: string;
|
||||
pk_field?: string;
|
||||
model?: ModelType;
|
||||
modelRenderer?: (instance: any) => ReactNode;
|
||||
filters?: any;
|
||||
child?: ApiFormFieldType;
|
||||
children?: { [key: string]: ApiFormFieldType };
|
||||
required?: boolean;
|
||||
error?: string;
|
||||
choices?: ApiFormFieldChoice[];
|
||||
hidden?: boolean;
|
||||
disabled?: boolean;
|
||||
exclude?: boolean;
|
||||
read_only?: boolean;
|
||||
placeholder?: string;
|
||||
description?: string;
|
||||
preFieldContent?: JSX.Element;
|
||||
postFieldContent?: JSX.Element;
|
||||
adjustValue?: (value: any) => any;
|
||||
onValueChange?: (value: any, record?: any) => void;
|
||||
adjustFilters?: (value: ApiFormAdjustFilterType) => any;
|
||||
addRow?: () => any;
|
||||
headers?: ApiFormFieldHeader[];
|
||||
depends_on?: string[];
|
||||
};
|
||||
|
||||
export type ApiFormFieldSet = Record<string, ApiFormFieldType>;
|
||||
|
||||
/**
|
||||
* Properties for the ApiForm component
|
||||
* @param url : The API endpoint to fetch the form data from
|
||||
* @param pk : Optional primary-key value when editing an existing object
|
||||
* @param pk_field : Optional primary-key field name (default: pk)
|
||||
* @param pathParams : Optional path params for the url
|
||||
* @param method : Optional HTTP method to use when submitting the form (default: GET)
|
||||
* @param fields : The fields to render in the form
|
||||
* @param submitText : Optional custom text to display on the submit button (default: Submit)4
|
||||
* @param submitColor : Optional custom color for the submit button (default: green)
|
||||
* @param fetchInitialData : Optional flag to fetch initial data from the server (default: true)
|
||||
* @param preFormContent : Optional content to render before the form fields
|
||||
* @param postFormContent : Optional content to render after the form fields
|
||||
* @param successMessage : Optional message to display on successful form submission
|
||||
* @param onFormSuccess : A callback function to call when the form is submitted successfully.
|
||||
* @param onFormError : A callback function to call when the form is submitted with errors.
|
||||
* @param processFormData : A callback function to process the form data before submission
|
||||
* @param checkClose: A callback function to check if the form can be closed after submission
|
||||
* @param modelType : Define a model type for this form
|
||||
* @param follow : Boolean, follow the result of the form (if possible)
|
||||
* @param table : Table to update on success (if provided)
|
||||
*/
|
||||
export interface ApiFormProps {
|
||||
url: ApiEndpoints | string;
|
||||
pk?: number | string;
|
||||
pk_field?: string;
|
||||
pathParams?: PathParams;
|
||||
queryParams?: URLSearchParams;
|
||||
method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
||||
fields?: ApiFormFieldSet;
|
||||
focus?: string;
|
||||
initialData?: FieldValues;
|
||||
submitText?: string;
|
||||
submitColor?: string;
|
||||
fetchInitialData?: boolean;
|
||||
ignorePermissionCheck?: boolean;
|
||||
preFormContent?: JSX.Element;
|
||||
preFormWarning?: string;
|
||||
preFormSuccess?: string;
|
||||
postFormContent?: JSX.Element;
|
||||
successMessage?: string | null;
|
||||
onFormSuccess?: (data: any, form: UseFormReturn) => void;
|
||||
onFormError?: (response: any, form: UseFormReturn) => void;
|
||||
processFormData?: (data: any, form: UseFormReturn) => any;
|
||||
checkClose?: (data: any, form: UseFormReturn) => boolean;
|
||||
table?: TableState;
|
||||
modelType?: ModelType;
|
||||
follow?: boolean;
|
||||
actions?: ApiFormAction[];
|
||||
timeout?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param title : The title to display in the modal header
|
||||
* @param cancelText : Optional custom text to display on the cancel button (default: Cancel)
|
||||
* @param cancelColor : Optional custom color for the cancel button (default: blue)
|
||||
* @param onClose : A callback function to call when the modal is closed.
|
||||
* @param onOpen : A callback function to call when the modal is opened.
|
||||
*/
|
||||
export interface ApiFormModalProps extends ApiFormProps {
|
||||
title: string;
|
||||
cancelText?: string;
|
||||
cancelColor?: string;
|
||||
onClose?: () => void;
|
||||
onOpen?: () => void;
|
||||
closeOnClickOutside?: boolean;
|
||||
size?: UiSizeType;
|
||||
}
|
||||
|
||||
export interface BulkEditApiFormModalProps extends ApiFormModalProps {
|
||||
items: number[];
|
||||
}
|
9
src/frontend/lib/types/Icons.tsx
Normal file
9
src/frontend/lib/types/Icons.tsx
Normal file
@ -0,0 +1,9 @@
|
||||
import type { Icon, IconProps } from '@tabler/icons-react';
|
||||
|
||||
export type TablerIconType = React.ForwardRefExoticComponent<
|
||||
Omit<IconProps, 'ref'> & React.RefAttributes<Icon>
|
||||
>;
|
||||
|
||||
export type InvenTreeIconType = {
|
||||
[key: string]: TablerIconType;
|
||||
};
|
17
src/frontend/lib/types/Modals.tsx
Normal file
17
src/frontend/lib/types/Modals.tsx
Normal file
@ -0,0 +1,17 @@
|
||||
import type { UiSizeType } from './Core';
|
||||
|
||||
export interface UseModalProps {
|
||||
title: string;
|
||||
children: React.ReactElement;
|
||||
size?: UiSizeType;
|
||||
onOpen?: () => void;
|
||||
onClose?: () => void;
|
||||
closeOnClickOutside?: boolean;
|
||||
}
|
||||
|
||||
export interface UseModalReturn {
|
||||
open: () => void;
|
||||
close: () => void;
|
||||
toggle: () => void;
|
||||
modal: React.ReactElement;
|
||||
}
|
87
src/frontend/lib/types/Plugins.tsx
Normal file
87
src/frontend/lib/types/Plugins.tsx
Normal file
@ -0,0 +1,87 @@
|
||||
import type { MantineColorScheme, MantineTheme } from '@mantine/core';
|
||||
import type { QueryClient } from '@tanstack/react-query';
|
||||
import type { AxiosInstance } from 'axios';
|
||||
import type { NavigateFunction } from 'react-router-dom';
|
||||
import type { ModelType } from '../enums/ModelType';
|
||||
import type { ApiFormModalProps, BulkEditApiFormModalProps } from './Forms';
|
||||
import type { UseModalReturn } from './Modals';
|
||||
import type { SettingsStateProps } from './Settings';
|
||||
import type { UserStateProps } from './User';
|
||||
|
||||
export interface PluginProps {
|
||||
name: string;
|
||||
slug: string;
|
||||
version: null | string;
|
||||
}
|
||||
|
||||
export interface PluginVersion {
|
||||
inventree: string;
|
||||
react: string;
|
||||
reactDom: string;
|
||||
mantine: string;
|
||||
}
|
||||
|
||||
export type InvenTreeFormsContext = {
|
||||
bulkEdit: (props: BulkEditApiFormModalProps) => UseModalReturn;
|
||||
create: (props: ApiFormModalProps) => UseModalReturn;
|
||||
delete: (props: ApiFormModalProps) => UseModalReturn;
|
||||
edit: (props: ApiFormModalProps) => UseModalReturn;
|
||||
};
|
||||
|
||||
/**
|
||||
* A set of properties which are passed to a plugin,
|
||||
* for rendering an element in the user interface.
|
||||
*
|
||||
* @param version - The version of the running InvenTree software stack
|
||||
* @param api - The Axios API instance (see ../states/ApiState.tsx)
|
||||
* @param user - The current user instance (see ../states/UserState.tsx)
|
||||
* @param userSettings - The current user settings (see ../states/SettingsState.tsx)
|
||||
* @param globalSettings - The global settings (see ../states/SettingsState.tsx)
|
||||
* @param navigate - The navigation function (see react-router-dom)
|
||||
* @param theme - The current Mantine theme
|
||||
* @param colorScheme - The current Mantine color scheme (e.g. 'light' / 'dark')
|
||||
* @param host - The current host URL
|
||||
* @param locale - The current locale string (e.g. 'en' / 'de')
|
||||
* @param model - The model type associated with the rendered component (if applicable)
|
||||
* @param id - The ID (primary key) of the model instance for the plugin (if applicable)
|
||||
* @param instance - The model instance data (if available)
|
||||
* @param reloadContent - A function which can be called to reload the plugin content
|
||||
* @param reloadInstance - A function which can be called to reload the model instance
|
||||
* @param context - Any additional context data which may be passed to the plugin
|
||||
*/
|
||||
export type InvenTreePluginContext = {
|
||||
version: PluginVersion;
|
||||
api: AxiosInstance;
|
||||
queryClient: QueryClient;
|
||||
user: UserStateProps;
|
||||
userSettings: SettingsStateProps;
|
||||
globalSettings: SettingsStateProps;
|
||||
host: string;
|
||||
locale: string;
|
||||
navigate: NavigateFunction;
|
||||
theme: MantineTheme;
|
||||
forms: InvenTreeFormsContext;
|
||||
colorScheme: MantineColorScheme;
|
||||
model?: ModelType | string;
|
||||
id?: string | number | null;
|
||||
instance?: any;
|
||||
reloadContent?: () => void;
|
||||
reloadInstance?: () => void;
|
||||
context?: any;
|
||||
};
|
||||
|
||||
/*
|
||||
* The version of the InvenTree plugin context interface.
|
||||
* This number should be incremented if the interface changes.
|
||||
*/
|
||||
|
||||
// @ts-ignore
|
||||
export const INVENTREE_PLUGIN_VERSION: string = __INVENTREE_LIB_VERSION__;
|
||||
// @ts-ignore
|
||||
export const INVENTREE_REACT_VERSION: string = __INVENTREE_REACT_VERSION__;
|
||||
// @ts-ignore
|
||||
export const INVENTREE_REACT_DOM_VERSION: string =
|
||||
// @ts-ignore
|
||||
__INVENTREE_REACT_DOM_VERSION__;
|
||||
// @ts-ignore
|
||||
export const INVENTREE_MANTINE_VERSION: string = __INVENTREE_MANTINE_VERSION__;
|
8
src/frontend/lib/types/Server.tsx
Normal file
8
src/frontend/lib/types/Server.tsx
Normal file
@ -0,0 +1,8 @@
|
||||
export interface Host {
|
||||
host: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface HostList {
|
||||
[key: string]: Host;
|
||||
}
|
55
src/frontend/lib/types/Settings.tsx
Normal file
55
src/frontend/lib/types/Settings.tsx
Normal file
@ -0,0 +1,55 @@
|
||||
import type { ApiEndpoints } from '../enums/ApiEndpoints';
|
||||
import type { PathParams } from './Core';
|
||||
|
||||
export enum SettingTyp {
|
||||
InvenTree = 'inventree',
|
||||
Plugin = 'plugin',
|
||||
User = 'user',
|
||||
Notification = 'notification'
|
||||
}
|
||||
|
||||
export enum SettingType {
|
||||
Boolean = 'boolean',
|
||||
Integer = 'integer',
|
||||
String = 'string',
|
||||
Choice = 'choice',
|
||||
Model = 'related field'
|
||||
}
|
||||
|
||||
// Type interface defining a single 'setting' object
|
||||
export interface Setting {
|
||||
pk: number;
|
||||
key: string;
|
||||
value: string;
|
||||
name: string;
|
||||
description: string;
|
||||
type: SettingType;
|
||||
units: string;
|
||||
choices: SettingChoice[];
|
||||
model_name: string | null;
|
||||
model_filters: Record<string, any> | null;
|
||||
api_url: string | null;
|
||||
typ: SettingTyp;
|
||||
plugin?: string;
|
||||
method?: string;
|
||||
required?: boolean;
|
||||
}
|
||||
|
||||
export interface SettingChoice {
|
||||
value: string;
|
||||
display_name: string;
|
||||
}
|
||||
|
||||
export type SettingsLookup = {
|
||||
[key: string]: string;
|
||||
};
|
||||
|
||||
export interface SettingsStateProps {
|
||||
settings: Setting[];
|
||||
lookup: SettingsLookup;
|
||||
fetchSettings: () => Promise<boolean>;
|
||||
endpoint: ApiEndpoints;
|
||||
pathParams?: PathParams;
|
||||
getSetting: (key: string, default_value?: string) => string; // Return a raw setting value
|
||||
isSet: (key: string, default_value?: boolean) => boolean; // Check a "boolean" setting
|
||||
}
|
69
src/frontend/lib/types/Tables.tsx
Normal file
69
src/frontend/lib/types/Tables.tsx
Normal file
@ -0,0 +1,69 @@
|
||||
import type { SetURLSearchParams } from 'react-router-dom';
|
||||
import type { FilterSetState } from './Filters';
|
||||
|
||||
/*
|
||||
* Type definition for representing the state of a table:
|
||||
*
|
||||
* tableKey: A unique key for the table. When this key changes, the table will be refreshed.
|
||||
* refreshTable: A callback function to externally refresh the table.
|
||||
* isLoading: A boolean flag to indicate if the table is currently loading data
|
||||
* setIsLoading: A function to set the isLoading flag
|
||||
* filterSet: A group of active filters
|
||||
* queryFilters: A map of query filters (e.g. ?active=true&overdue=false) passed in the URL
|
||||
* setQueryFilters: A function to set the query filters
|
||||
* clearQueryFilters: A function to clear all query filters
|
||||
* expandedRecords: An array of expanded records (rows) in the table
|
||||
* setExpandedRecords: A function to set the expanded records
|
||||
* isRowExpanded: A function to determine if a record is expanded
|
||||
* selectedRecords: An array of selected records (rows) in the table
|
||||
* selectedIds: An array of primary key values for selected records
|
||||
* hasSelectedRecords: A boolean flag to indicate if any records are selected
|
||||
* setSelectedRecords: A function to set the selected records
|
||||
* clearSelectedRecords: A function to clear all selected records
|
||||
* hiddenColumns: An array of hidden column names
|
||||
* setHiddenColumns: A function to set the hidden columns
|
||||
* searchTerm: The current search term for the table
|
||||
* setSearchTerm: A function to set the search term
|
||||
* recordCount: The total number of records in the table
|
||||
* setRecordCount: A function to set the record count
|
||||
* page: The current page number
|
||||
* setPage: A function to set the current page number
|
||||
* pageSize: The number of records per page
|
||||
* setPageSize: A function to set the number of records per page
|
||||
* records: An array of records (rows) in the table
|
||||
* setRecords: A function to set the records
|
||||
* updateRecord: A function to update a single record in the table
|
||||
* idAccessor: The name of the primary key field in the records (default = 'pk')
|
||||
*/
|
||||
export type TableState = {
|
||||
tableKey: string;
|
||||
refreshTable: () => void;
|
||||
isLoading: boolean;
|
||||
setIsLoading: (value: boolean) => void;
|
||||
filterSet: FilterSetState;
|
||||
queryFilters: URLSearchParams;
|
||||
setQueryFilters: SetURLSearchParams;
|
||||
clearQueryFilters: () => void;
|
||||
expandedRecords: any[];
|
||||
setExpandedRecords: (records: any[]) => void;
|
||||
isRowExpanded: (pk: number) => boolean;
|
||||
selectedRecords: any[];
|
||||
selectedIds: any[];
|
||||
hasSelectedRecords: boolean;
|
||||
setSelectedRecords: (records: any[]) => void;
|
||||
clearSelectedRecords: () => void;
|
||||
hiddenColumns: string[];
|
||||
setHiddenColumns: (columns: string[]) => void;
|
||||
searchTerm: string;
|
||||
setSearchTerm: (term: string) => void;
|
||||
recordCount: number;
|
||||
setRecordCount: (count: number) => void;
|
||||
page: number;
|
||||
setPage: (page: number) => void;
|
||||
pageSize: number;
|
||||
setPageSize: (pageSize: number) => void;
|
||||
records: any[];
|
||||
setRecords: (records: any[]) => void;
|
||||
updateRecord: (record: any) => void;
|
||||
idAccessor?: string;
|
||||
};
|
62
src/frontend/lib/types/User.tsx
Normal file
62
src/frontend/lib/types/User.tsx
Normal file
@ -0,0 +1,62 @@
|
||||
import type { ModelType } from '../enums/ModelType';
|
||||
import type { UserPermissions, UserRoles } from '../enums/Roles';
|
||||
|
||||
export interface UserProfile {
|
||||
language: string;
|
||||
theme: any;
|
||||
widgets: any;
|
||||
displayname: string | null;
|
||||
position: string | null;
|
||||
status: string | null;
|
||||
location: string | null;
|
||||
active: boolean;
|
||||
contact: string | null;
|
||||
type: string;
|
||||
organisation: string | null;
|
||||
primary_group: number | null;
|
||||
}
|
||||
|
||||
// Type interface fully defining the current user
|
||||
export interface UserProps {
|
||||
pk: number;
|
||||
username: string;
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
email: string;
|
||||
is_staff?: boolean;
|
||||
is_superuser?: boolean;
|
||||
roles?: Record<string, string[]>;
|
||||
permissions?: Record<string, string[]>;
|
||||
groups: any[] | null;
|
||||
profile: UserProfile;
|
||||
}
|
||||
|
||||
export interface UserStateProps {
|
||||
user: UserProps | undefined;
|
||||
is_authed: boolean;
|
||||
userId: () => number | undefined;
|
||||
username: () => string;
|
||||
setAuthenticated: (authed?: boolean) => void;
|
||||
fetchUserToken: () => Promise<void>;
|
||||
setUser: (newUser: UserProps | undefined) => void;
|
||||
getUser: () => UserProps | undefined;
|
||||
fetchUserState: () => Promise<void>;
|
||||
clearUserState: () => void;
|
||||
checkUserRole: (role: UserRoles, permission: UserPermissions) => boolean;
|
||||
hasDeleteRole: (role: UserRoles) => boolean;
|
||||
hasChangeRole: (role: UserRoles) => boolean;
|
||||
hasAddRole: (role: UserRoles) => boolean;
|
||||
hasViewRole: (role: UserRoles) => boolean;
|
||||
checkUserPermission: (
|
||||
model: ModelType,
|
||||
permission: UserPermissions
|
||||
) => boolean;
|
||||
hasDeletePermission: (model: ModelType) => boolean;
|
||||
hasChangePermission: (model: ModelType) => boolean;
|
||||
hasAddPermission: (model: ModelType) => boolean;
|
||||
hasViewPermission: (model: ModelType) => boolean;
|
||||
isAuthed: () => boolean;
|
||||
isLoggedIn: () => boolean;
|
||||
isStaff: () => boolean;
|
||||
isSuperuser: () => boolean;
|
||||
}
|
@ -1,11 +1,40 @@
|
||||
{
|
||||
"name": "inventreeui",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"name": "@inventreedb/ui",
|
||||
"description": "UI components for the InvenTree project",
|
||||
"version": "0.0.8",
|
||||
"private": false,
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
"inventree"
|
||||
],
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": {
|
||||
".": "./dist/index.js"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"lib",
|
||||
"LICENSE",
|
||||
"README.md",
|
||||
"CHANGELOG.md"
|
||||
],
|
||||
"homepage": "https://inventree.org",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/inventree/inventree"
|
||||
},
|
||||
"author": {
|
||||
"name": "InvenTree Developers",
|
||||
"email": "support@inventree.org",
|
||||
"url": "https://inventree.org",
|
||||
"org": "InvenTree"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc && vite build",
|
||||
"build": "tsc && vite build --emptyOutDir",
|
||||
"lib": "tsc --p ./tsconfig.lib.json && vite --config vite.lib.config.ts build",
|
||||
"preview": "vite preview",
|
||||
"extract": "lingui extract",
|
||||
"compile": "lingui compile --typescript"
|
||||
@ -101,6 +130,7 @@
|
||||
"typescript": "^5.8.2",
|
||||
"vite": "^6.2.6",
|
||||
"vite-plugin-babel-macros": "^1.0.6",
|
||||
"vite-plugin-dts": "^4.5.3",
|
||||
"vite-plugin-istanbul": "^6.0.2"
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +1,16 @@
|
||||
import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
|
||||
import { ModelInformationDict } from '@lib/enums/ModelInformation';
|
||||
import type { ModelType } from '@lib/enums/ModelType';
|
||||
import { apiUrl } from '@lib/functions/Api';
|
||||
import { getDetailUrl } from '@lib/functions/Navigation';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { Box, Divider, Modal } from '@mantine/core';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { type NavigateFunction, useNavigate } from 'react-router-dom';
|
||||
import { api } from '../../App';
|
||||
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||
import type { ModelType } from '../../enums/ModelType';
|
||||
import { extractErrorMessage } from '../../functions/api';
|
||||
import { getDetailUrl } from '../../functions/urls';
|
||||
import { apiUrl } from '../../states/ApiState';
|
||||
import { useUserState } from '../../states/UserState';
|
||||
import { StylishText } from '../items/StylishText';
|
||||
import { ModelInformationDict } from '../render/ModelType';
|
||||
import { BarcodeInput } from './BarcodeInput';
|
||||
|
||||
export default function BarcodeScanDialog({
|
||||
|
@ -1,4 +1,4 @@
|
||||
import type { ModelType } from '../../enums/ModelType';
|
||||
import type { ModelType } from '@lib/enums/ModelType';
|
||||
|
||||
/**
|
||||
* Interface defining a single barcode scan item
|
||||
|
@ -18,9 +18,9 @@ import { useQuery } from '@tanstack/react-query';
|
||||
import QR from 'qrcode';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
|
||||
import { apiUrl } from '@lib/functions/Api';
|
||||
import { api } from '../../App';
|
||||
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||
import { apiUrl } from '../../states/ApiState';
|
||||
import { useGlobalSettingsState } from '../../states/SettingsState';
|
||||
import { CopyButton } from '../buttons/CopyButton';
|
||||
import type { QrCodeType } from '../items/ActionDropdown';
|
||||
|
@ -2,11 +2,11 @@ import { t } from '@lingui/core/macro';
|
||||
import { IconUserStar } from '@tabler/icons-react';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
|
||||
import type { ModelType } from '../../enums/ModelType';
|
||||
import { ModelInformationDict } from '@lib/enums/ModelInformation';
|
||||
import type { ModelType } from '@lib/enums/ModelType';
|
||||
import { generateUrl } from '../../functions/urls';
|
||||
import { useServerApiState } from '../../states/ApiState';
|
||||
import { useUserState } from '../../states/UserState';
|
||||
import { ModelInformationDict } from '../render/ModelType';
|
||||
import { ActionButton } from './ActionButton';
|
||||
|
||||
export type AdminButtonProps = {
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { Button, Tooltip } from '@mantine/core';
|
||||
|
||||
import { InvenTreeIcon, type InvenTreeIconType } from '../../functions/icons';
|
||||
import type { InvenTreeIconType } from '@lib/types/Icons';
|
||||
import { InvenTreeIcon } from '../../functions/icons';
|
||||
|
||||
/**
|
||||
* A "primary action" button for display on a page detail, (for example)
|
||||
@ -15,7 +16,7 @@ export default function PrimaryActionButton({
|
||||
}: Readonly<{
|
||||
title: string;
|
||||
tooltip?: string;
|
||||
icon?: InvenTreeIconType;
|
||||
icon?: keyof InvenTreeIconType;
|
||||
color?: string;
|
||||
hidden?: boolean;
|
||||
onClick: () => void;
|
||||
|
@ -1,19 +1,19 @@
|
||||
import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
|
||||
import type { ModelType } from '@lib/enums/ModelType';
|
||||
import { apiUrl } from '@lib/functions/Api';
|
||||
import type { ApiFormFieldSet } from '@lib/types/Forms';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { IconPrinter, IconReport, IconTags } from '@tabler/icons-react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { api } from '../../App';
|
||||
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||
import type { ModelType } from '../../enums/ModelType';
|
||||
import { extractAvailableFields } from '../../functions/forms';
|
||||
import useDataOutput from '../../hooks/UseDataOutput';
|
||||
import { useCreateApiFormModal } from '../../hooks/UseForm';
|
||||
import { apiUrl } from '../../states/ApiState';
|
||||
import {
|
||||
useGlobalSettingsState,
|
||||
useUserSettingsState
|
||||
} from '../../states/SettingsState';
|
||||
import type { ApiFormFieldSet } from '../forms/fields/ApiFormField';
|
||||
import { ActionDropdown } from '../items/ActionDropdown';
|
||||
|
||||
export function PrintingActions({
|
||||
|
@ -14,9 +14,9 @@ import {
|
||||
IconLogin
|
||||
} from '@tabler/icons-react';
|
||||
|
||||
import type { AuthProvider } from '@lib/types/Auth';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { ProviderLogin } from '../../functions/auth';
|
||||
import type { Provider } from '../../states/states';
|
||||
|
||||
const brandIcons: { [key: string]: JSX.Element } = {
|
||||
google: <IconBrandGoogle />,
|
||||
@ -32,7 +32,7 @@ const brandIcons: { [key: string]: JSX.Element } = {
|
||||
microsoft: <IconBrandAzure />
|
||||
};
|
||||
|
||||
export function SsoButton({ provider }: Readonly<{ provider: Provider }>) {
|
||||
export function SsoButton({ provider }: Readonly<{ provider: AuthProvider }>) {
|
||||
return (
|
||||
<Tooltip
|
||||
label={t`You will be redirected to the provider for further actions.`}
|
||||
@ -48,6 +48,6 @@ export function SsoButton({ provider }: Readonly<{ provider: Provider }>) {
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
function getBrandIcon(provider: Provider) {
|
||||
function getBrandIcon(provider: AuthProvider) {
|
||||
return brandIcons[provider.id] || <IconLogin />;
|
||||
}
|
||||
|
@ -10,8 +10,8 @@ import {
|
||||
import { IconChevronDown } from '@tabler/icons-react';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import type { TablerIconType } from '@lib/types/Icons';
|
||||
import { identifierString } from '../../functions/conversion';
|
||||
import type { TablerIconType } from '../../functions/icons';
|
||||
import * as classes from './SplitButton.css';
|
||||
|
||||
interface SplitButtonOption {
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
|
||||
import { ModelType } from '@lib/enums/ModelType';
|
||||
import { apiUrl } from '@lib/functions/Api';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { showNotification } from '@mantine/notifications';
|
||||
import { IconBell } from '@tabler/icons-react';
|
||||
import { useApi } from '../../contexts/ApiContext';
|
||||
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||
import { ModelType } from '../../enums/ModelType';
|
||||
import { apiUrl } from '../../states/ApiState';
|
||||
import { ActionButton } from './ActionButton';
|
||||
|
||||
export default function StarredToggleButton({
|
||||
|
@ -4,6 +4,7 @@ import dayGridPlugin from '@fullcalendar/daygrid';
|
||||
import interactionPlugin from '@fullcalendar/interaction';
|
||||
import FullCalendar from '@fullcalendar/react';
|
||||
|
||||
import type { TableFilter } from '@lib/types/Filters';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import {
|
||||
ActionIcon,
|
||||
@ -27,7 +28,6 @@ import {
|
||||
import { useCallback, useState } from 'react';
|
||||
import type { CalendarState } from '../../hooks/UseCalendar';
|
||||
import { useLocalState } from '../../states/LocalState';
|
||||
import type { TableFilter } from '../../tables/Filter';
|
||||
import { FilterSelectDrawer } from '../../tables/FilterSelectDrawer';
|
||||
import { TableSearchInput } from '../../tables/Search';
|
||||
import { Boundary } from '../Boundary';
|
||||
|
@ -3,6 +3,13 @@ import type {
|
||||
EventClickArg,
|
||||
EventContentArg
|
||||
} from '@fullcalendar/core';
|
||||
import { ModelInformationDict } from '@lib/enums/ModelInformation';
|
||||
import type { ModelType } from '@lib/enums/ModelType';
|
||||
import type { UserRoles } from '@lib/enums/Roles';
|
||||
import { apiUrl } from '@lib/functions/Api';
|
||||
import { getDetailUrl } from '@lib/functions/Navigation';
|
||||
import { navigateToLink } from '@lib/functions/Navigation';
|
||||
import type { TableFilter } from '@lib/types/Filters';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { ActionIcon, Group, Text } from '@mantine/core';
|
||||
import { hideNotification, showNotification } from '@mantine/notifications';
|
||||
@ -15,22 +22,15 @@ import dayjs from 'dayjs';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { api } from '../../App';
|
||||
import type { ModelType } from '../../enums/ModelType';
|
||||
import type { UserRoles } from '../../enums/Roles';
|
||||
import { navigateToLink } from '../../functions/navigation';
|
||||
import { getDetailUrl } from '../../functions/urls';
|
||||
import useCalendar from '../../hooks/UseCalendar';
|
||||
import { apiUrl } from '../../states/ApiState';
|
||||
import { useUserState } from '../../states/UserState';
|
||||
import {
|
||||
AssignedToMeFilter,
|
||||
HasProjectCodeFilter,
|
||||
OrderStatusFilter,
|
||||
ProjectCodeFilter,
|
||||
ResponsibleFilter,
|
||||
type TableFilter
|
||||
ResponsibleFilter
|
||||
} from '../../tables/Filter';
|
||||
import { ModelInformationDict } from '../render/ModelType';
|
||||
import { StatusRenderer } from '../render/StatusRenderer';
|
||||
import Calendar from './Calendar';
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { t } from '@lingui/core/macro';
|
||||
|
||||
import { ModelType } from '../../enums/ModelType';
|
||||
import { ModelType } from '@lib/enums/ModelType';
|
||||
import { useGlobalSettingsState } from '../../states/SettingsState';
|
||||
import type { DashboardWidgetProps } from './DashboardWidget';
|
||||
import ColorToggleDashboardWidget from './widgets/ColorToggleWidget';
|
||||
|
@ -14,10 +14,10 @@ import { IconMailCheck } from '@tabler/icons-react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
|
||||
import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
|
||||
import { apiUrl } from '@lib/functions/Api';
|
||||
import { api } from '../../../App';
|
||||
import { formatDate } from '../../../defaults/formatters';
|
||||
import { ApiEndpoints } from '../../../enums/ApiEndpoints';
|
||||
import { apiUrl } from '../../../states/ApiState';
|
||||
import { useUserState } from '../../../states/UserState';
|
||||
import { StylishText } from '../../items/StylishText';
|
||||
|
||||
|
@ -4,17 +4,15 @@ import { useQuery } from '@tanstack/react-query';
|
||||
import { type ReactNode, useCallback } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { ModelInformationDict } from '@lib/enums/ModelInformation';
|
||||
import type { ModelType } from '@lib/enums/ModelType';
|
||||
import { apiUrl } from '@lib/functions/Api';
|
||||
import { navigateToLink } from '@lib/functions/Navigation';
|
||||
import type { InvenTreeIconType } from '@lib/types/Icons';
|
||||
import { useApi } from '../../../contexts/ApiContext';
|
||||
import type { ModelType } from '../../../enums/ModelType';
|
||||
import {
|
||||
InvenTreeIcon,
|
||||
type InvenTreeIconType
|
||||
} from '../../../functions/icons';
|
||||
import { navigateToLink } from '../../../functions/navigation';
|
||||
import { apiUrl } from '../../../states/ApiState';
|
||||
import { InvenTreeIcon } from '../../../functions/icons';
|
||||
import { useUserState } from '../../../states/UserState';
|
||||
import { StylishText } from '../../items/StylishText';
|
||||
import { ModelInformationDict } from '../../render/ModelType';
|
||||
import type { DashboardWidgetProps } from '../DashboardWidget';
|
||||
|
||||
/**
|
||||
@ -28,7 +26,7 @@ function QueryCountWidget({
|
||||
}: Readonly<{
|
||||
modelType: ModelType;
|
||||
title: string;
|
||||
icon?: InvenTreeIconType;
|
||||
icon?: keyof InvenTreeIconType;
|
||||
params: any;
|
||||
}>): ReactNode {
|
||||
const api = useApi();
|
||||
|
@ -17,14 +17,15 @@ import { getValueAtPath } from 'mantine-datatable';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
|
||||
import { ModelType } from '@lib/enums/ModelType';
|
||||
import { apiUrl } from '@lib/functions/Api';
|
||||
import { getDetailUrl } from '@lib/functions/Navigation';
|
||||
import { navigateToLink } from '@lib/functions/Navigation';
|
||||
import type { InvenTreeIconType } from '@lib/types/Icons';
|
||||
import { useApi } from '../../contexts/ApiContext';
|
||||
import { formatDate } from '../../defaults/formatters';
|
||||
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||
import { ModelType } from '../../enums/ModelType';
|
||||
import { InvenTreeIcon, type InvenTreeIconType } from '../../functions/icons';
|
||||
import { navigateToLink } from '../../functions/navigation';
|
||||
import { getDetailUrl } from '../../functions/urls';
|
||||
import { apiUrl } from '../../states/ApiState';
|
||||
import { InvenTreeIcon } from '../../functions/icons';
|
||||
import { useGlobalSettingsState } from '../../states/SettingsState';
|
||||
import { CopyButton } from '../buttons/CopyButton';
|
||||
import { YesNoButton } from '../buttons/YesNoButton';
|
||||
@ -35,7 +36,7 @@ import { StatusRenderer } from '../render/StatusRenderer';
|
||||
|
||||
export type DetailsField = {
|
||||
hidden?: boolean;
|
||||
icon?: InvenTreeIconType;
|
||||
icon?: keyof InvenTreeIconType;
|
||||
name: string;
|
||||
label?: string;
|
||||
badge?: BadgeType;
|
||||
@ -464,7 +465,7 @@ export function DetailsTableField({
|
||||
<Table.Td style={{ minWidth: 75, lineBreak: 'auto', flex: 2 }}>
|
||||
<Group gap='xs' wrap='nowrap'>
|
||||
<InvenTreeIcon
|
||||
icon={field.icon ?? (field.name as InvenTreeIconType)}
|
||||
icon={field.icon ?? (field.name as keyof InvenTreeIconType)}
|
||||
/>
|
||||
<Text style={{ paddingLeft: 10 }}>{field.label}</Text>
|
||||
</Group>
|
||||
|
@ -21,10 +21,10 @@ import { useHover } from '@mantine/hooks';
|
||||
import { modals } from '@mantine/modals';
|
||||
import { useMemo, useState } from 'react';
|
||||
|
||||
import type { UserRoles } from '@lib/enums/Roles';
|
||||
import { cancelEvent } from '@lib/functions/Events';
|
||||
import { showNotification } from '@mantine/notifications';
|
||||
import { api } from '../../App';
|
||||
import type { UserRoles } from '../../enums/Roles';
|
||||
import { cancelEvent } from '../../functions/events';
|
||||
import { InvenTreeIcon } from '../../functions/icons';
|
||||
import { showApiErrorMessage } from '../../functions/notifications';
|
||||
import { useEditApiFormModal } from '../../hooks/UseForm';
|
||||
|
@ -7,11 +7,11 @@ import 'easymde/dist/easymde.min.css';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import SimpleMDE from 'react-simplemde-editor';
|
||||
|
||||
import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
|
||||
import { ModelInformationDict } from '@lib/enums/ModelInformation';
|
||||
import type { ModelType } from '@lib/enums/ModelType';
|
||||
import { apiUrl } from '@lib/functions/Api';
|
||||
import { useApi } from '../../contexts/ApiContext';
|
||||
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||
import type { ModelType } from '../../enums/ModelType';
|
||||
import { apiUrl } from '../../states/ApiState';
|
||||
import { ModelInformationDict } from '../render/ModelType';
|
||||
|
||||
/*
|
||||
* A text editor component for editing notes against a model type and instance.
|
||||
|
@ -24,14 +24,14 @@ import Split from '@uiw/react-split';
|
||||
import type React from 'react';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import { ModelInformationDict } from '@lib/enums/ModelInformation';
|
||||
import { ModelType } from '@lib/enums/ModelType';
|
||||
import { apiUrl } from '@lib/functions/Api';
|
||||
import { api } from '../../../App';
|
||||
import { ModelType } from '../../../enums/ModelType';
|
||||
import { apiUrl } from '../../../states/ApiState';
|
||||
import type { TemplateI } from '../../../tables/settings/TemplateTable';
|
||||
import { Boundary } from '../../Boundary';
|
||||
import { SplitButton } from '../../buttons/SplitButton';
|
||||
import { StandaloneField } from '../../forms/StandaloneField';
|
||||
import { ModelInformationDict } from '../../render/ModelType';
|
||||
|
||||
type EditorProps = {
|
||||
template: TemplateI;
|
||||
|
@ -2,7 +2,6 @@ import { t } from '@lingui/core/macro';
|
||||
import {
|
||||
Alert,
|
||||
Button,
|
||||
type DefaultMantineColor,
|
||||
Divider,
|
||||
Group,
|
||||
LoadingOverlay,
|
||||
@ -19,14 +18,17 @@ import {
|
||||
FormProvider,
|
||||
type SubmitErrorHandler,
|
||||
type SubmitHandler,
|
||||
type UseFormReturn,
|
||||
useForm
|
||||
} from 'react-hook-form';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { type NavigateFunction, useNavigate } from 'react-router-dom';
|
||||
|
||||
import { getDetailUrl } from '@lib/functions/Navigation';
|
||||
import type {
|
||||
ApiFormFieldSet,
|
||||
ApiFormFieldType,
|
||||
ApiFormProps
|
||||
} from '@lib/types/Forms';
|
||||
import { useApi } from '../../contexts/ApiContext';
|
||||
import type { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||
import type { ModelType } from '../../enums/ModelType';
|
||||
import {
|
||||
type NestedDict,
|
||||
constructField,
|
||||
@ -38,74 +40,8 @@ import {
|
||||
invalidResponse,
|
||||
showTimeoutNotification
|
||||
} from '../../functions/notifications';
|
||||
import { getDetailUrl } from '../../functions/urls';
|
||||
import type { TableState } from '../../hooks/UseTable';
|
||||
import type { PathParams } from '../../states/ApiState';
|
||||
import { Boundary } from '../Boundary';
|
||||
import {
|
||||
ApiFormField,
|
||||
type ApiFormFieldSet,
|
||||
type ApiFormFieldType
|
||||
} from './fields/ApiFormField';
|
||||
|
||||
export interface ApiFormAction {
|
||||
text: string;
|
||||
variant?: 'outline';
|
||||
color?: DefaultMantineColor;
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Properties for the ApiForm component
|
||||
* @param url : The API endpoint to fetch the form data from
|
||||
* @param pk : Optional primary-key value when editing an existing object
|
||||
* @param pk_field : Optional primary-key field name (default: pk)
|
||||
* @param pathParams : Optional path params for the url
|
||||
* @param method : Optional HTTP method to use when submitting the form (default: GET)
|
||||
* @param fields : The fields to render in the form
|
||||
* @param submitText : Optional custom text to display on the submit button (default: Submit)4
|
||||
* @param submitColor : Optional custom color for the submit button (default: green)
|
||||
* @param fetchInitialData : Optional flag to fetch initial data from the server (default: true)
|
||||
* @param preFormContent : Optional content to render before the form fields
|
||||
* @param postFormContent : Optional content to render after the form fields
|
||||
* @param successMessage : Optional message to display on successful form submission
|
||||
* @param onFormSuccess : A callback function to call when the form is submitted successfully.
|
||||
* @param onFormError : A callback function to call when the form is submitted with errors.
|
||||
* @param processFormData : A callback function to process the form data before submission
|
||||
* @param checkClose: A callback function to check if the form can be closed after submission
|
||||
* @param modelType : Define a model type for this form
|
||||
* @param follow : Boolean, follow the result of the form (if possible)
|
||||
* @param table : Table to update on success (if provided)
|
||||
*/
|
||||
export interface ApiFormProps {
|
||||
url: ApiEndpoints | string;
|
||||
pk?: number | string;
|
||||
pk_field?: string;
|
||||
pathParams?: PathParams;
|
||||
queryParams?: URLSearchParams;
|
||||
method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
||||
fields?: ApiFormFieldSet;
|
||||
focus?: string;
|
||||
initialData?: FieldValues;
|
||||
submitText?: string;
|
||||
submitColor?: string;
|
||||
fetchInitialData?: boolean;
|
||||
ignorePermissionCheck?: boolean;
|
||||
preFormContent?: JSX.Element;
|
||||
preFormWarning?: string;
|
||||
preFormSuccess?: string;
|
||||
postFormContent?: JSX.Element;
|
||||
successMessage?: string | null;
|
||||
onFormSuccess?: (data: any, form: UseFormReturn) => void;
|
||||
onFormError?: (response: any, form: UseFormReturn) => void;
|
||||
processFormData?: (data: any, form: UseFormReturn) => any;
|
||||
checkClose?: (data: any, form: UseFormReturn) => boolean;
|
||||
table?: TableState;
|
||||
modelType?: ModelType;
|
||||
follow?: boolean;
|
||||
actions?: ApiFormAction[];
|
||||
timeout?: number;
|
||||
}
|
||||
import { ApiFormField } from './fields/ApiFormField';
|
||||
|
||||
export function OptionsApiForm({
|
||||
props: _props,
|
||||
@ -219,7 +155,16 @@ export function ApiForm({
|
||||
}>) {
|
||||
const api = useApi();
|
||||
const queryClient = useQueryClient();
|
||||
const navigate = useNavigate();
|
||||
|
||||
// Accessor for the navigation function (which is used to redirect the user)
|
||||
let navigate: NavigateFunction | null = null;
|
||||
|
||||
try {
|
||||
navigate = useNavigate();
|
||||
} catch (_error) {
|
||||
// Note: If we launch a form within a plugin context, useNavigate() may not be available
|
||||
navigate = null;
|
||||
}
|
||||
|
||||
const [fields, setFields] = useState<ApiFormFieldSet>(
|
||||
() => props.fields ?? {}
|
||||
@ -482,7 +427,9 @@ export function ApiForm({
|
||||
|
||||
if (props.follow && props.modelType && response.data?.pk) {
|
||||
// If we want to automatically follow the returned data
|
||||
if (!!navigate) {
|
||||
navigate(getDetailUrl(props.modelType, response.data?.pk));
|
||||
}
|
||||
} else if (props.table) {
|
||||
// If we want to automatically update or reload a linked table
|
||||
const pk_field = props.pk_field ?? 'pk';
|
||||
|
@ -15,9 +15,10 @@ import { useDisclosure } from '@mantine/hooks';
|
||||
import { useState } from 'react';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
|
||||
import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
|
||||
import { apiUrl } from '@lib/functions/Api';
|
||||
import { showNotification } from '@mantine/notifications';
|
||||
import { api } from '../../App';
|
||||
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||
import {
|
||||
doBasicLogin,
|
||||
doSimpleLogin,
|
||||
@ -25,7 +26,7 @@ import {
|
||||
followRedirect
|
||||
} from '../../functions/auth';
|
||||
import { showLoginNotification } from '../../functions/notifications';
|
||||
import { apiUrl, useServerApiState } from '../../states/ApiState';
|
||||
import { useServerApiState } from '../../states/ApiState';
|
||||
import { useUserState } from '../../states/UserState';
|
||||
import { SsoButton } from '../buttons/SSOButton';
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
import type { HostList } from '@lib/types/Server';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import {
|
||||
@ -13,8 +14,6 @@ import { useForm } from '@mantine/form';
|
||||
import { randomId } from '@mantine/hooks';
|
||||
import { IconSquarePlus, IconTrash } from '@tabler/icons-react';
|
||||
|
||||
import type { HostList } from '../../states/states';
|
||||
|
||||
export function HostOptionsForm({
|
||||
data,
|
||||
saveOptions
|
||||
|
@ -20,10 +20,10 @@ import {
|
||||
IconServerSpark
|
||||
} from '@tabler/icons-react';
|
||||
|
||||
import type { HostList } from '@lib/types/Server';
|
||||
import { Wrapper } from '../../pages/Auth/Layout';
|
||||
import { useServerApiState } from '../../states/ApiState';
|
||||
import { useLocalState } from '../../states/LocalState';
|
||||
import type { HostList } from '../../states/states';
|
||||
import { ActionButton } from '../buttons/ActionButton';
|
||||
import { HostOptionsForm } from './HostOptionsForm';
|
||||
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { FormProvider, useForm } from 'react-hook-form';
|
||||
|
||||
import { ApiFormField, type ApiFormFieldType } from './fields/ApiFormField';
|
||||
import type { ApiFormFieldType } from '@lib/types/Forms';
|
||||
import { ApiFormField } from './fields/ApiFormField';
|
||||
|
||||
export function StandaloneField({
|
||||
fieldDefinition,
|
||||
|
@ -1,18 +1,10 @@
|
||||
import { t } from '@lingui/core/macro';
|
||||
import {
|
||||
Alert,
|
||||
FileInput,
|
||||
type MantineStyleProp,
|
||||
NumberInput,
|
||||
Stack,
|
||||
Switch
|
||||
} from '@mantine/core';
|
||||
import type { UseFormReturnType } from '@mantine/form';
|
||||
import { Alert, FileInput, NumberInput, Stack, Switch } from '@mantine/core';
|
||||
import { useId } from '@mantine/hooks';
|
||||
import { type ReactNode, useCallback, useEffect, useMemo } from 'react';
|
||||
import { useCallback, useEffect, useMemo } from 'react';
|
||||
import { type Control, type FieldValues, useController } from 'react-hook-form';
|
||||
|
||||
import type { ModelType } from '../../../enums/ModelType';
|
||||
import type { ApiFormFieldSet, ApiFormFieldType } from '@lib/types/Forms';
|
||||
import { isTrue } from '../../../functions/conversion';
|
||||
import { ChoiceField } from './ChoiceField';
|
||||
import DateField from './DateField';
|
||||
@ -23,103 +15,6 @@ import { RelatedModelField } from './RelatedModelField';
|
||||
import { TableField } from './TableField';
|
||||
import TextField from './TextField';
|
||||
|
||||
export type ApiFormData = UseFormReturnType<Record<string, unknown>>;
|
||||
|
||||
export type ApiFormAdjustFilterType = {
|
||||
filters: any;
|
||||
data: FieldValues;
|
||||
};
|
||||
|
||||
export type ApiFormFieldChoice = {
|
||||
value: any;
|
||||
display_name: string;
|
||||
};
|
||||
|
||||
// Define individual headers in a table field
|
||||
export type ApiFormFieldHeader = {
|
||||
title: string;
|
||||
style?: MantineStyleProp;
|
||||
};
|
||||
|
||||
/** Definition of the ApiForm field component.
|
||||
* - The 'name' attribute *must* be provided
|
||||
* - All other attributes are optional, and may be provided by the API
|
||||
* - However, they can be overridden by the user
|
||||
*
|
||||
* @param name : The name of the field
|
||||
* @param label : The label to display for the field
|
||||
* @param value : The value of the field
|
||||
* @param default : The default value of the field
|
||||
* @param icon : An icon to display next to the field
|
||||
* @param field_type : The type of field to render
|
||||
* @param api_url : The API endpoint to fetch data from (for related fields)
|
||||
* @param pk_field : The primary key field for the related field (default = "pk")
|
||||
* @param model : The model to use for related fields
|
||||
* @param filters : Optional API filters to apply to related fields
|
||||
* @param required : Whether the field is required
|
||||
* @param hidden : Whether the field is hidden
|
||||
* @param disabled : Whether the field is disabled
|
||||
* @param error : Optional error message to display
|
||||
* @param exclude : Whether to exclude the field from the submitted data
|
||||
* @param placeholder : The placeholder text to display
|
||||
* @param description : The description to display for the field
|
||||
* @param preFieldContent : Content to render before the field
|
||||
* @param postFieldContent : Content to render after the field
|
||||
* @param onValueChange : Callback function to call when the field value changes
|
||||
* @param adjustFilters : Callback function to adjust the filters for a related field before a query is made
|
||||
* @param adjustValue : Callback function to adjust the value of the field before it is sent to the API
|
||||
* @param addRow : Callback function to add a new row to a table field
|
||||
* @param onKeyDown : Callback function to get which key was pressed in the form to handle submission on enter
|
||||
*/
|
||||
export type ApiFormFieldType = {
|
||||
label?: string;
|
||||
value?: any;
|
||||
default?: any;
|
||||
icon?: ReactNode;
|
||||
field_type?:
|
||||
| 'related field'
|
||||
| 'email'
|
||||
| 'url'
|
||||
| 'string'
|
||||
| 'icon'
|
||||
| 'boolean'
|
||||
| 'date'
|
||||
| 'datetime'
|
||||
| 'integer'
|
||||
| 'decimal'
|
||||
| 'float'
|
||||
| 'number'
|
||||
| 'choice'
|
||||
| 'file upload'
|
||||
| 'nested object'
|
||||
| 'dependent field'
|
||||
| 'table';
|
||||
api_url?: string;
|
||||
pk_field?: string;
|
||||
model?: ModelType;
|
||||
modelRenderer?: (instance: any) => ReactNode;
|
||||
filters?: any;
|
||||
child?: ApiFormFieldType;
|
||||
children?: { [key: string]: ApiFormFieldType };
|
||||
required?: boolean;
|
||||
error?: string;
|
||||
choices?: ApiFormFieldChoice[];
|
||||
hidden?: boolean;
|
||||
disabled?: boolean;
|
||||
exclude?: boolean;
|
||||
read_only?: boolean;
|
||||
placeholder?: string;
|
||||
description?: string;
|
||||
preFieldContent?: JSX.Element;
|
||||
postFieldContent?: JSX.Element;
|
||||
adjustValue?: (value: any) => any;
|
||||
onValueChange?: (value: any, record?: any) => void;
|
||||
adjustFilters?: (value: ApiFormAdjustFilterType) => any;
|
||||
addRow?: () => any;
|
||||
headers?: ApiFormFieldHeader[];
|
||||
depends_on?: string[];
|
||||
};
|
||||
|
||||
/**
|
||||
* Render an individual form field
|
||||
*/
|
||||
@ -384,5 +279,3 @@ export function ApiFormField({
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
export type ApiFormFieldSet = Record<string, ApiFormFieldType>;
|
||||
|
@ -1,10 +1,9 @@
|
||||
import type { ApiFormFieldType } from '@lib/types/Forms';
|
||||
import { Select } from '@mantine/core';
|
||||
import { useId } from '@mantine/hooks';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import type { FieldValues, UseControllerReturn } from 'react-hook-form';
|
||||
|
||||
import type { ApiFormFieldType } from './ApiFormField';
|
||||
|
||||
/**
|
||||
* Render a 'select' field for selecting from a list of choices
|
||||
*/
|
||||
|
@ -1,11 +1,10 @@
|
||||
import type { ApiFormFieldType } from '@lib/types/Forms';
|
||||
import { DateInput } from '@mantine/dates';
|
||||
import dayjs from 'dayjs';
|
||||
import customParseFormat from 'dayjs/plugin/customParseFormat';
|
||||
import { useCallback, useId, useMemo } from 'react';
|
||||
import type { FieldValues, UseControllerReturn } from 'react-hook-form';
|
||||
|
||||
import type { ApiFormFieldType } from './ApiFormField';
|
||||
|
||||
dayjs.extend(customParseFormat);
|
||||
|
||||
export default function DateField({
|
||||
|
@ -5,16 +5,13 @@ import {
|
||||
useFormContext
|
||||
} from 'react-hook-form';
|
||||
|
||||
import type { ApiFormFieldSet, ApiFormFieldType } from '@lib/types/Forms';
|
||||
import { useApi } from '../../../contexts/ApiContext';
|
||||
import {
|
||||
constructField,
|
||||
extractAvailableFields
|
||||
} from '../../../functions/forms';
|
||||
import {
|
||||
ApiFormField,
|
||||
type ApiFormFieldSet,
|
||||
type ApiFormFieldType
|
||||
} from './ApiFormField';
|
||||
import { ApiFormField } from './ApiFormField';
|
||||
|
||||
export function DependentField({
|
||||
control,
|
||||
|
@ -21,9 +21,9 @@ import { startTransition, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import type { FieldValues, UseControllerReturn } from 'react-hook-form';
|
||||
import { FixedSizeGrid as Grid } from 'react-window';
|
||||
|
||||
import type { ApiFormFieldType } from '@lib/types/Forms';
|
||||
import { useIconState } from '../../../states/IconState';
|
||||
import { ApiIcon } from '../../items/ApiIcon';
|
||||
import type { ApiFormFieldType } from './ApiFormField';
|
||||
|
||||
export default function IconField({
|
||||
controller,
|
||||
|
@ -1,11 +1,7 @@
|
||||
import type { ApiFormFieldSet, ApiFormFieldType } from '@lib/types/Forms';
|
||||
import { Accordion, Divider, Stack, Text } from '@mantine/core';
|
||||
import type { Control, FieldValues } from 'react-hook-form';
|
||||
|
||||
import {
|
||||
ApiFormField,
|
||||
type ApiFormFieldSet,
|
||||
type ApiFormFieldType
|
||||
} from './ApiFormField';
|
||||
import { ApiFormField } from './ApiFormField';
|
||||
|
||||
export function NestedObjectField({
|
||||
control,
|
||||
|
@ -15,10 +15,10 @@ import {
|
||||
} from 'react-hook-form';
|
||||
import Select from 'react-select';
|
||||
|
||||
import type { ApiFormFieldType } from '@lib/types/Forms';
|
||||
import { useApi } from '../../../contexts/ApiContext';
|
||||
import { vars } from '../../../theme';
|
||||
import { RenderInstance } from '../../render/Instance';
|
||||
import type { ApiFormFieldType } from './ApiFormField';
|
||||
|
||||
/**
|
||||
* Render a 'select' field for searching the database against a particular model type
|
||||
|
@ -5,11 +5,11 @@ import { IconExclamationCircle } from '@tabler/icons-react';
|
||||
import { type ReactNode, useCallback, useEffect, useMemo } from 'react';
|
||||
import type { FieldValues, UseControllerReturn } from 'react-hook-form';
|
||||
|
||||
import type { ApiFormFieldType } from '@lib/types/Forms';
|
||||
import { identifierString } from '../../../functions/conversion';
|
||||
import { InvenTreeIcon } from '../../../functions/icons';
|
||||
import { AddItemButton } from '../../buttons/AddItemButton';
|
||||
import { StandaloneField } from '../StandaloneField';
|
||||
import type { ApiFormFieldType } from './ApiFormField';
|
||||
|
||||
export interface TableFieldRowProps {
|
||||
item: any;
|
||||
|
@ -9,18 +9,19 @@ import {
|
||||
} from '@tabler/icons-react';
|
||||
import { type ReactNode, useCallback, useMemo, useState } from 'react';
|
||||
|
||||
import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
|
||||
import { apiUrl } from '@lib/functions/Api';
|
||||
import { cancelEvent } from '@lib/functions/Events';
|
||||
import type { TableFilter } from '@lib/types/Filters';
|
||||
import type { ApiFormFieldSet } from '@lib/types/Forms';
|
||||
import { useApi } from '../../contexts/ApiContext';
|
||||
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||
import { cancelEvent } from '../../functions/events';
|
||||
import {
|
||||
useDeleteApiFormModal,
|
||||
useEditApiFormModal
|
||||
} from '../../hooks/UseForm';
|
||||
import type { ImportSessionState } from '../../hooks/UseImportSession';
|
||||
import { useTable } from '../../hooks/UseTable';
|
||||
import { apiUrl } from '../../states/ApiState';
|
||||
import type { TableColumn } from '../../tables/Column';
|
||||
import type { TableFilter } from '../../tables/Filter';
|
||||
import { InvenTreeTable } from '../../tables/InvenTreeTable';
|
||||
import {
|
||||
type RowAction,
|
||||
@ -29,7 +30,6 @@ import {
|
||||
} from '../../tables/RowActions';
|
||||
import { ActionButton } from '../buttons/ActionButton';
|
||||
import { YesNoButton } from '../buttons/YesNoButton';
|
||||
import type { ApiFormFieldSet } from '../forms/fields/ApiFormField';
|
||||
import { ProgressBar } from '../items/ProgressBar';
|
||||
import { RenderRemoteInstance } from '../render/Instance';
|
||||
|
||||
|
@ -13,12 +13,12 @@ import {
|
||||
import { IconCheck } from '@tabler/icons-react';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
|
||||
import { apiUrl } from '@lib/functions/Api';
|
||||
import type { ApiFormFieldType } from '@lib/types/Forms';
|
||||
import { useApi } from '../../contexts/ApiContext';
|
||||
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||
import type { ImportSessionState } from '../../hooks/UseImportSession';
|
||||
import { apiUrl } from '../../states/ApiState';
|
||||
import { StandaloneField } from '../forms/StandaloneField';
|
||||
import type { ApiFormFieldType } from '../forms/fields/ApiFormField';
|
||||
|
||||
function ImporterColumn({
|
||||
column,
|
||||
|
@ -16,7 +16,7 @@ import {
|
||||
import { IconCheck } from '@tabler/icons-react';
|
||||
import { type ReactNode, useMemo } from 'react';
|
||||
|
||||
import { ModelType } from '../../enums/ModelType';
|
||||
import { ModelType } from '@lib/enums/ModelType';
|
||||
import { useImportSession } from '../../hooks/UseImportSession';
|
||||
import useStatusCodes from '../../hooks/UseStatusCodes';
|
||||
import { StylishText } from '../items/StylishText';
|
||||
|
@ -3,7 +3,7 @@ import { Center, Container, Loader, Stack, Text } from '@mantine/core';
|
||||
import { useInterval } from '@mantine/hooks';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { ModelType } from '../../enums/ModelType';
|
||||
import { ModelType } from '@lib/enums/ModelType';
|
||||
import type { ImportSessionState } from '../../hooks/UseImportSession';
|
||||
import useStatusCodes from '../../hooks/UseStatusCodes';
|
||||
import { StylishText } from '../items/StylishText';
|
||||
|
@ -20,7 +20,7 @@ import {
|
||||
} from '@tabler/icons-react';
|
||||
import { type ReactNode, useMemo } from 'react';
|
||||
|
||||
import type { ModelType } from '../../enums/ModelType';
|
||||
import type { ModelType } from '@lib/enums/ModelType';
|
||||
import { identifierString } from '../../functions/conversion';
|
||||
import { InvenTreeIcon } from '../../functions/icons';
|
||||
import { InvenTreeQRCode, QRCodeLink, QRCodeUnlink } from '../barcodes/QRCode';
|
||||
|
@ -11,15 +11,16 @@ import {
|
||||
import { useMemo } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { InvenTreeIcon, type InvenTreeIconType } from '../../functions/icons';
|
||||
import { navigateToLink } from '../../functions/navigation';
|
||||
import { navigateToLink } from '@lib/functions/Navigation';
|
||||
import type { InvenTreeIconType } from '@lib/types/Icons';
|
||||
import { InvenTreeIcon } from '../../functions/icons';
|
||||
import { StylishText } from './StylishText';
|
||||
|
||||
export interface MenuLinkItem {
|
||||
id: string;
|
||||
title: string | JSX.Element;
|
||||
description?: string;
|
||||
icon?: InvenTreeIconType;
|
||||
icon?: keyof InvenTreeIconType;
|
||||
action?: () => void;
|
||||
link?: string;
|
||||
external?: boolean;
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
|
||||
import { apiUrl } from '@lib/functions/Api';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import {
|
||||
@ -13,8 +15,6 @@ import { notifications } from '@mantine/notifications';
|
||||
import { IconCircleCheck, IconReload } from '@tabler/icons-react';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { api } from '../../App';
|
||||
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||
import { apiUrl } from '../../states/ApiState';
|
||||
|
||||
export interface RuleSet {
|
||||
pk?: number;
|
||||
|
@ -3,33 +3,19 @@ import {
|
||||
Text,
|
||||
darken,
|
||||
getThemeColor,
|
||||
lighten,
|
||||
useMantineColorScheme,
|
||||
useMantineTheme
|
||||
} from '@mantine/core';
|
||||
import { useMemo } from 'react';
|
||||
import { useLocalState } from '../../states/LocalState';
|
||||
|
||||
// Hook that memoizes the gradient color based on the primary color of the theme
|
||||
const useThematicGradient = () => {
|
||||
const { usertheme } = useLocalState();
|
||||
const theme = useMantineTheme();
|
||||
const colorScheme = useMantineColorScheme();
|
||||
|
||||
const primary = useMemo(() => {
|
||||
return getThemeColor(usertheme.primaryColor, theme);
|
||||
}, [usertheme.primaryColor, theme]);
|
||||
return getThemeColor(theme.primaryColor, theme);
|
||||
}, [theme]);
|
||||
|
||||
const secondary = useMemo(() => {
|
||||
let secondary = primary;
|
||||
if (colorScheme.colorScheme == 'dark') {
|
||||
secondary = lighten(primary, 0.3);
|
||||
} else {
|
||||
secondary = darken(primary, 0.3);
|
||||
}
|
||||
|
||||
return secondary;
|
||||
}, [usertheme, colorScheme, primary]);
|
||||
const secondary = useMemo(() => darken(primary, 0.25), [primary]);
|
||||
|
||||
return useMemo(() => {
|
||||
return { primary, secondary };
|
||||
|
@ -14,10 +14,11 @@ import {
|
||||
import type { ContextModalProps } from '@mantine/modals';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
|
||||
import { apiUrl } from '@lib/functions/Api';
|
||||
import { api } from '../../App';
|
||||
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||
import { generateUrl } from '../../functions/urls';
|
||||
import { apiUrl, useServerApiState } from '../../states/ApiState';
|
||||
import { useServerApiState } from '../../states/ApiState';
|
||||
import { useUserState } from '../../states/UserState';
|
||||
import { CopyButton } from '../buttons/CopyButton';
|
||||
import { StylishText } from '../items/StylishText';
|
||||
|
@ -14,9 +14,9 @@ import {
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
|
||||
import { apiUrl } from '@lib/functions/Api';
|
||||
import { api } from '../../App';
|
||||
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||
import { apiUrl } from '../../states/ApiState';
|
||||
|
||||
export function LicenceView(entries: Readonly<any[]>) {
|
||||
return (
|
||||
|
@ -10,8 +10,8 @@ import { IconMenu2 } from '@tabler/icons-react';
|
||||
import { useMemo } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { navigateToLink } from '@lib/functions/Navigation';
|
||||
import { identifierString } from '../../functions/conversion';
|
||||
import { navigateToLink } from '../../functions/navigation';
|
||||
|
||||
export type Breadcrumb = {
|
||||
icon?: React.ReactNode;
|
||||
|
@ -4,7 +4,7 @@ import { useCallback, useMemo } from 'react';
|
||||
import { Link, Route, Routes, useNavigate, useParams } from 'react-router-dom';
|
||||
import type { To } from 'react-router-dom';
|
||||
|
||||
import type { UiSizeType } from '../../defaults/formatters';
|
||||
import type { UiSizeType } from '@lib/types/Core';
|
||||
import { useLocalState } from '../../states/LocalState';
|
||||
import { StylishText } from '../items/StylishText';
|
||||
import * as classes from './DetailDrawer.css';
|
||||
|
@ -13,13 +13,14 @@ import { useQuery } from '@tanstack/react-query';
|
||||
import { type ReactNode, useEffect, useMemo, useState } from 'react';
|
||||
import { useMatch, useNavigate } from 'react-router-dom';
|
||||
|
||||
import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
|
||||
import { apiUrl } from '@lib/functions/Api';
|
||||
import { navigateToLink } from '@lib/functions/Navigation';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { api } from '../../App';
|
||||
import { getNavTabs } from '../../defaults/links';
|
||||
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||
import { navigateToLink } from '../../functions/navigation';
|
||||
import * as classes from '../../main.css';
|
||||
import { apiUrl, useServerApiState } from '../../states/ApiState';
|
||||
import { useServerApiState } from '../../states/ApiState';
|
||||
import { useLocalState } from '../../states/LocalState';
|
||||
import {
|
||||
useGlobalSettingsState,
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { LoadingOverlay } from '@mantine/core';
|
||||
|
||||
import type { ModelType } from '../../enums/ModelType';
|
||||
import type { UserRoles } from '../../enums/Roles';
|
||||
import type { ModelType } from '@lib/enums/ModelType';
|
||||
import type { UserRoles } from '@lib/enums/Roles';
|
||||
import { useUserState } from '../../states/UserState';
|
||||
import ClientError from '../errors/ClientError';
|
||||
import PermissionDenied from '../errors/PermissionDenied';
|
||||
|
@ -10,9 +10,9 @@ import {
|
||||
import { useViewportSize } from '@mantine/hooks';
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import { ModelType } from '@lib/enums/ModelType';
|
||||
import { UserRoles } from '@lib/enums/Roles';
|
||||
import { AboutLinks, DocumentationLinks } from '../../defaults/links';
|
||||
import { ModelType } from '../../enums/ModelType';
|
||||
import { UserRoles } from '../../enums/Roles';
|
||||
import useInstanceName from '../../hooks/UseInstanceName';
|
||||
import * as classes from '../../main.css';
|
||||
import { useGlobalSettingsState } from '../../states/SettingsState';
|
||||
|
@ -21,12 +21,12 @@ import { useQuery } from '@tanstack/react-query';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import type { ApiEndpoints } from '@lib/enums/ApiEndpoints';
|
||||
import type { ModelType } from '@lib/enums/ModelType';
|
||||
import { apiUrl } from '@lib/functions/Api';
|
||||
import { getDetailUrl } from '@lib/functions/Navigation';
|
||||
import { navigateToLink } from '@lib/functions/Navigation';
|
||||
import { useApi } from '../../contexts/ApiContext';
|
||||
import type { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||
import type { ModelType } from '../../enums/ModelType';
|
||||
import { navigateToLink } from '../../functions/navigation';
|
||||
import { getDetailUrl } from '../../functions/urls';
|
||||
import { apiUrl } from '../../states/ApiState';
|
||||
import { ApiIcon } from '../items/ApiIcon';
|
||||
import { StylishText } from '../items/StylishText';
|
||||
|
||||
|
@ -18,17 +18,17 @@ import { useQuery } from '@tanstack/react-query';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
|
||||
import { ModelInformationDict } from '@lib/enums/ModelInformation';
|
||||
import type { ModelType } from '@lib/enums/ModelType';
|
||||
import { apiUrl } from '@lib/functions/Api';
|
||||
import { getDetailUrl } from '@lib/functions/Navigation';
|
||||
import { getBaseUrl } from '@lib/functions/Navigation';
|
||||
import { navigateToLink } from '@lib/functions/Navigation';
|
||||
import { api } from '../../App';
|
||||
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||
import type { ModelType } from '../../enums/ModelType';
|
||||
import { navigateToLink } from '../../functions/navigation';
|
||||
import { getDetailUrl } from '../../functions/urls';
|
||||
import { getBaseUrl } from '../../main';
|
||||
import { apiUrl } from '../../states/ApiState';
|
||||
import { useUserState } from '../../states/UserState';
|
||||
import { Boundary } from '../Boundary';
|
||||
import { StylishText } from '../items/StylishText';
|
||||
import { ModelInformationDict } from '../render/ModelType';
|
||||
|
||||
/**
|
||||
* Render a single notification entry in the drawer
|
||||
|
@ -32,19 +32,20 @@ import { useQuery } from '@tanstack/react-query';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { type NavigateFunction, useNavigate } from 'react-router-dom';
|
||||
|
||||
import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
|
||||
import { ModelInformationDict } from '@lib/enums/ModelInformation';
|
||||
import { ModelType } from '@lib/enums/ModelType';
|
||||
import { UserRoles } from '@lib/enums/Roles';
|
||||
import { apiUrl } from '@lib/functions/Api';
|
||||
import { cancelEvent } from '@lib/functions/Events';
|
||||
import { navigateToLink } from '@lib/functions/Navigation';
|
||||
import { showNotification } from '@mantine/notifications';
|
||||
import { api } from '../../App';
|
||||
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||
import { ModelType } from '../../enums/ModelType';
|
||||
import { UserRoles } from '../../enums/Roles';
|
||||
import { cancelEvent } from '../../functions/events';
|
||||
import { navigateToLink } from '../../functions/navigation';
|
||||
import { apiUrl } from '../../states/ApiState';
|
||||
import { useUserSettingsState } from '../../states/SettingsState';
|
||||
import { useUserState } from '../../states/UserState';
|
||||
import { Boundary } from '../Boundary';
|
||||
import { RenderInstance } from '../render/Instance';
|
||||
import { ModelInformationDict, getModelInfo } from '../render/ModelType';
|
||||
import { getModelInfo } from '../render/ModelType';
|
||||
|
||||
// Define type for handling individual search queries
|
||||
type SearchQuery = {
|
||||
|
@ -2,7 +2,7 @@ import { t } from '@lingui/core/macro';
|
||||
import { Skeleton } from '@mantine/core';
|
||||
import { IconPaperclip } from '@tabler/icons-react';
|
||||
|
||||
import type { ModelType } from '../../enums/ModelType';
|
||||
import type { ModelType } from '@lib/enums/ModelType';
|
||||
import { AttachmentTable } from '../../tables/general/AttachmentTable';
|
||||
import type { PanelType } from './Panel';
|
||||
|
||||
|
@ -2,7 +2,7 @@ import { t } from '@lingui/core/macro';
|
||||
import { Skeleton } from '@mantine/core';
|
||||
import { IconNotes } from '@tabler/icons-react';
|
||||
|
||||
import type { ModelType } from '../../enums/ModelType';
|
||||
import type { ModelType } from '@lib/enums/ModelType';
|
||||
import { useUserState } from '../../states/UserState';
|
||||
import NotesEditor from '../editors/NotesEditor';
|
||||
import type { PanelType } from './Panel';
|
||||
|
@ -28,10 +28,10 @@ import {
|
||||
useParams
|
||||
} from 'react-router-dom';
|
||||
|
||||
import type { ModelType } from '../../enums/ModelType';
|
||||
import type { ModelType } from '@lib/enums/ModelType';
|
||||
import { cancelEvent } from '@lib/functions/Events';
|
||||
import { navigateToLink } from '@lib/functions/Navigation';
|
||||
import { identifierString } from '../../functions/conversion';
|
||||
import { cancelEvent } from '../../functions/events';
|
||||
import { navigateToLink } from '../../functions/navigation';
|
||||
import { usePluginPanels } from '../../hooks/UsePluginPanels';
|
||||
import { useLocalState } from '../../states/LocalState';
|
||||
import { Boundary } from '../Boundary';
|
||||
@ -47,6 +47,7 @@ import * as classes from './PanelGroup.css';
|
||||
* @param model - The target model for this panel group (e.g. 'part' / 'salesorder')
|
||||
* @param id - The target ID for this panel group (set to *null* for groups which do not target a specific model instance)
|
||||
* @param instance - The target model instance for this panel group
|
||||
* @param reloadInstance - Function to reload the model instance
|
||||
* @param selectedPanel - The currently selected panel
|
||||
* @param onPanelChange - Callback when the active panel changes
|
||||
* @param collapsible - If true, the panel group can be collapsed (defaults to true)
|
||||
@ -55,6 +56,7 @@ export type PanelProps = {
|
||||
pageKey: string;
|
||||
panels: PanelType[];
|
||||
instance?: any;
|
||||
reloadInstance?: () => void;
|
||||
model?: ModelType | string;
|
||||
id?: number | null;
|
||||
selectedPanel?: string;
|
||||
@ -67,6 +69,7 @@ function BasePanelGroup({
|
||||
panels,
|
||||
onPanelChange,
|
||||
selectedPanel,
|
||||
reloadInstance,
|
||||
instance,
|
||||
model,
|
||||
id,
|
||||
@ -82,9 +85,10 @@ function BasePanelGroup({
|
||||
|
||||
// Hook to load plugins for this panel
|
||||
const pluginPanelSet = usePluginPanels({
|
||||
id: id,
|
||||
model: model,
|
||||
instance: instance,
|
||||
id: id
|
||||
reloadFunc: reloadInstance
|
||||
});
|
||||
|
||||
// Rebuild the list of panels
|
||||
|
@ -1,12 +1,12 @@
|
||||
import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
|
||||
import { apiUrl } from '@lib/functions/Api';
|
||||
import type { ApiFormFieldSet } from '@lib/types/Forms';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { IconRadar } from '@tabler/icons-react';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||
import { useCreateApiFormModal } from '../../hooks/UseForm';
|
||||
import { usePluginsWithMixin } from '../../hooks/UsePlugins';
|
||||
import { apiUrl } from '../../states/ApiState';
|
||||
import { ActionButton } from '../buttons/ActionButton';
|
||||
import type { ApiFormFieldSet } from '../forms/fields/ApiFormField';
|
||||
import type { PluginInterface } from './PluginInterface';
|
||||
|
||||
export default function LocateItemButton({
|
||||
|
@ -1,51 +1,27 @@
|
||||
import {
|
||||
type MantineColorScheme,
|
||||
type MantineTheme,
|
||||
useMantineColorScheme,
|
||||
useMantineTheme
|
||||
} from '@mantine/core';
|
||||
import type { AxiosInstance } from 'axios';
|
||||
import { useMantineColorScheme, useMantineTheme } from '@mantine/core';
|
||||
import { useMemo } from 'react';
|
||||
import { type NavigateFunction, useNavigate } from 'react-router-dom';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import type { QueryClient } from '@tanstack/react-query';
|
||||
import { api, queryClient } from '../../App';
|
||||
import { useLocalState } from '../../states/LocalState';
|
||||
import {
|
||||
type SettingsStateProps,
|
||||
useGlobalSettingsState,
|
||||
useUserSettingsState
|
||||
} from '../../states/SettingsState';
|
||||
import { type UserStateProps, useUserState } from '../../states/UserState';
|
||||
import { useUserState } from '../../states/UserState';
|
||||
|
||||
/**
|
||||
* A set of properties which are passed to a plugin,
|
||||
* for rendering an element in the user interface.
|
||||
*
|
||||
* @param api - The Axios API instance (see ../states/ApiState.tsx)
|
||||
* @param user - The current user instance (see ../states/UserState.tsx)
|
||||
* @param userSettings - The current user settings (see ../states/SettingsState.tsx)
|
||||
* @param globalSettings - The global settings (see ../states/SettingsState.tsx)
|
||||
* @param navigate - The navigation function (see react-router-dom)
|
||||
* @param theme - The current Mantine theme
|
||||
* @param colorScheme - The current Mantine color scheme (e.g. 'light' / 'dark')
|
||||
* @param host - The current host URL
|
||||
* @param locale - The current locale string (e.g. 'en' / 'de')
|
||||
* @param context - Any additional context data which may be passed to the plugin
|
||||
*/
|
||||
export type InvenTreeContext = {
|
||||
api: AxiosInstance;
|
||||
queryClient: QueryClient;
|
||||
user: UserStateProps;
|
||||
userSettings: SettingsStateProps;
|
||||
globalSettings: SettingsStateProps;
|
||||
host: string;
|
||||
locale: string;
|
||||
navigate: NavigateFunction;
|
||||
theme: MantineTheme;
|
||||
colorScheme: MantineColorScheme;
|
||||
context?: any;
|
||||
};
|
||||
import {
|
||||
INVENTREE_MANTINE_VERSION,
|
||||
INVENTREE_PLUGIN_VERSION,
|
||||
INVENTREE_REACT_VERSION,
|
||||
type InvenTreePluginContext
|
||||
} from '@lib/types/Plugins';
|
||||
import {
|
||||
useBulkEditApiFormModal,
|
||||
useCreateApiFormModal,
|
||||
useDeleteApiFormModal,
|
||||
useEditApiFormModal
|
||||
} from '../../hooks/UseForm';
|
||||
|
||||
export const useInvenTreeContext = () => {
|
||||
const [locale, host] = useLocalState((s) => [s.language, s.host]);
|
||||
@ -56,8 +32,14 @@ export const useInvenTreeContext = () => {
|
||||
const globalSettings = useGlobalSettingsState();
|
||||
const userSettings = useUserSettingsState();
|
||||
|
||||
const contextData = useMemo<InvenTreeContext>(() => {
|
||||
const contextData = useMemo<InvenTreePluginContext>(() => {
|
||||
return {
|
||||
version: {
|
||||
inventree: INVENTREE_PLUGIN_VERSION,
|
||||
react: INVENTREE_REACT_VERSION,
|
||||
reactDom: INVENTREE_REACT_VERSION,
|
||||
mantine: INVENTREE_MANTINE_VERSION
|
||||
},
|
||||
user: user,
|
||||
host: host,
|
||||
locale: locale,
|
||||
@ -67,7 +49,13 @@ export const useInvenTreeContext = () => {
|
||||
globalSettings: globalSettings,
|
||||
userSettings: userSettings,
|
||||
theme: theme,
|
||||
colorScheme: colorScheme
|
||||
colorScheme: colorScheme,
|
||||
forms: {
|
||||
bulkEdit: useBulkEditApiFormModal,
|
||||
create: useCreateApiFormModal,
|
||||
delete: useDeleteApiFormModal,
|
||||
edit: useEditApiFormModal
|
||||
}
|
||||
};
|
||||
}, [
|
||||
user,
|
||||
|
@ -4,7 +4,7 @@ import { IconExclamationCircle } from '@tabler/icons-react';
|
||||
import { useMemo } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||
import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
|
||||
import { useInstance } from '../../hooks/UseInstance';
|
||||
import { InfoItem } from '../items/InfoItem';
|
||||
import { StylishText } from '../items/StylishText';
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Stack } from '@mantine/core';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
import type { InvenTreeContext } from './PluginContext';
|
||||
import type { InvenTreePluginContext } from '@lib/types/Plugins';
|
||||
import type { PluginUIFeature } from './PluginUIFeature';
|
||||
import RemoteComponent from './RemoteComponent';
|
||||
|
||||
@ -23,7 +23,7 @@ export default function PluginPanelContent({
|
||||
pluginContext
|
||||
}: Readonly<{
|
||||
pluginFeature: PluginUIFeature;
|
||||
pluginContext: InvenTreeContext;
|
||||
pluginContext: InvenTreePluginContext;
|
||||
}>): ReactNode {
|
||||
return (
|
||||
<Stack gap='xs'>
|
||||
|
@ -1,3 +1,4 @@
|
||||
import type { InvenTreePluginContext } from '@lib/types/Plugins';
|
||||
import { useInvenTreeContext } from './PluginContext';
|
||||
import RemoteComponent from './RemoteComponent';
|
||||
|
||||
@ -21,13 +22,13 @@ export default function PluginSettingsPanel({
|
||||
}: Readonly<{
|
||||
pluginAdmin: PluginAdminInterface;
|
||||
}>) {
|
||||
const pluginContext = useInvenTreeContext();
|
||||
const ctx: InvenTreePluginContext = useInvenTreeContext();
|
||||
|
||||
return (
|
||||
<RemoteComponent
|
||||
source={pluginAdmin.source}
|
||||
defaultFunctionName='renderPluginSettings'
|
||||
context={{ ...pluginContext, context: pluginAdmin.context }}
|
||||
context={{ ...ctx, context: pluginAdmin.context }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
import type { ModelType } from '../../enums/ModelType';
|
||||
import type { InvenTreeIconType } from '../../functions/icons';
|
||||
import type { ModelType } from '@lib/enums/ModelType';
|
||||
import type { InvenTreeIconType } from '@lib/types/Icons';
|
||||
import type { InvenTreePluginContext } from '@lib/types/Plugins';
|
||||
import type { TemplateI } from '../../tables/settings/TemplateTable';
|
||||
import type { TemplateEditorProps } from '../editors/TemplateEditor/TemplateEditor';
|
||||
import type { InvenTreeContext } from './PluginContext';
|
||||
import type { PluginUIFeature } from './PluginUIFeature';
|
||||
|
||||
// #region Type Helpers
|
||||
@ -19,7 +19,7 @@ export type PluginUIGetFeatureType<
|
||||
ServerContext extends Record<string, unknown>
|
||||
> = (params: {
|
||||
featureContext: T['featureContext'];
|
||||
inventreeContext: InvenTreeContext;
|
||||
inventreeContext: InvenTreePluginContext;
|
||||
serverContext: ServerContext;
|
||||
}) => T['featureReturnType'];
|
||||
|
||||
|
@ -1,11 +1,15 @@
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { Alert, Stack, Text } from '@mantine/core';
|
||||
import { Alert, MantineProvider, Stack, Text } from '@mantine/core';
|
||||
import { IconExclamationCircle } from '@tabler/icons-react';
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import type { InvenTreePluginContext } from '@lib/types/Plugins';
|
||||
import { type Root, createRoot } from 'react-dom/client';
|
||||
import { api, queryClient } from '../../App';
|
||||
import { ApiProvider } from '../../contexts/ApiContext';
|
||||
import { LanguageContext } from '../../contexts/LanguageContext';
|
||||
import { identifierString } from '../../functions/conversion';
|
||||
import { Boundary } from '../Boundary';
|
||||
import type { InvenTreeContext } from './PluginContext';
|
||||
import { findExternalPluginFunction } from './PluginSource';
|
||||
|
||||
/**
|
||||
@ -24,9 +28,16 @@ export default function RemoteComponent({
|
||||
}: Readonly<{
|
||||
source: string;
|
||||
defaultFunctionName: string;
|
||||
context: InvenTreeContext;
|
||||
context: InvenTreePluginContext;
|
||||
}>) {
|
||||
const componentRef = useRef<HTMLDivElement>();
|
||||
const [rootElement, setRootElement] = useState<Root | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (componentRef.current && !rootElement) {
|
||||
setRootElement(createRoot(componentRef.current));
|
||||
}
|
||||
}, [componentRef.current]);
|
||||
|
||||
const [renderingError, setRenderingError] = useState<string | undefined>(
|
||||
undefined
|
||||
@ -47,20 +58,45 @@ export default function RemoteComponent({
|
||||
return defaultFunctionName;
|
||||
}, [source, defaultFunctionName]);
|
||||
|
||||
const reloadPluginContent = async () => {
|
||||
if (!componentRef.current) {
|
||||
const reloadPluginContent = useCallback(() => {
|
||||
if (!rootElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ctx: InvenTreePluginContext = {
|
||||
...context,
|
||||
reloadContent: reloadPluginContent
|
||||
};
|
||||
|
||||
if (sourceFile && functionName) {
|
||||
findExternalPluginFunction(sourceFile, functionName)
|
||||
.then((func) => {
|
||||
if (func) {
|
||||
if (!!func) {
|
||||
try {
|
||||
func(componentRef.current, context);
|
||||
if (func.length > 1) {
|
||||
// Support "legacy" plugin functions which call createRoot() internally
|
||||
// Ref: https://github.com/inventree/InvenTree/pull/9439/
|
||||
func(componentRef.current, ctx);
|
||||
} else {
|
||||
// Render the plugin component into the target element
|
||||
// Note that we have to provide the right context(s) to the component
|
||||
// This approach ensures that the component is rendered in the correct context tree
|
||||
rootElement.render(
|
||||
<ApiProvider client={queryClient} api={api}>
|
||||
<MantineProvider
|
||||
theme={ctx.theme}
|
||||
defaultColorScheme={ctx.colorScheme}
|
||||
>
|
||||
<LanguageContext>{func(ctx)}</LanguageContext>
|
||||
</MantineProvider>
|
||||
</ApiProvider>
|
||||
);
|
||||
}
|
||||
|
||||
setRenderingError('');
|
||||
} catch (error) {
|
||||
setRenderingError(`${error}`);
|
||||
console.error(error);
|
||||
}
|
||||
} else {
|
||||
setRenderingError(`${sourceFile}:${functionName}`);
|
||||
@ -76,12 +112,12 @@ export default function RemoteComponent({
|
||||
`${t`Invalid source or function name`} - ${sourceFile}:${functionName}`
|
||||
);
|
||||
}
|
||||
};
|
||||
}, [componentRef, rootElement, sourceFile, functionName, context]);
|
||||
|
||||
// Reload the plugin content dynamically
|
||||
useEffect(() => {
|
||||
reloadPluginContent();
|
||||
}, [sourceFile, functionName, context]);
|
||||
}, [sourceFile, functionName, context, rootElement]);
|
||||
|
||||
return (
|
||||
<Boundary
|
||||
@ -99,7 +135,7 @@ export default function RemoteComponent({
|
||||
</Text>
|
||||
</Alert>
|
||||
)}
|
||||
<div ref={componentRef as any} />
|
||||
{componentRef && <div ref={componentRef as any} />}
|
||||
</Stack>
|
||||
</Boundary>
|
||||
);
|
||||
|
@ -1,7 +1,7 @@
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
import { ModelType } from '../../enums/ModelType';
|
||||
import { getDetailUrl } from '../../functions/urls';
|
||||
import { ModelType } from '@lib/enums/ModelType';
|
||||
import { getDetailUrl } from '@lib/functions/Navigation';
|
||||
import { type InstanceRenderInterface, RenderInlineModel } from './Instance';
|
||||
import { StatusRenderer } from './StatusRenderer';
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { Text } from '@mantine/core';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
import { ModelType } from '../../enums/ModelType';
|
||||
import { getDetailUrl } from '../../functions/urls';
|
||||
import { ModelType } from '@lib/enums/ModelType';
|
||||
import { getDetailUrl } from '@lib/functions/Navigation';
|
||||
import { type InstanceRenderInterface, RenderInlineModel } from './Instance';
|
||||
|
||||
/**
|
||||
|
@ -3,11 +3,12 @@ import { Alert, Anchor, Group, Skeleton, Space, Text } from '@mantine/core';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { type ReactNode, useCallback } from 'react';
|
||||
|
||||
import { ModelInformationDict } from '@lib/enums/ModelInformation';
|
||||
import { ModelType } from '@lib/enums/ModelType';
|
||||
import { apiUrl } from '@lib/functions/Api';
|
||||
import { navigateToLink } from '@lib/functions/Navigation';
|
||||
import { useApi } from '../../contexts/ApiContext';
|
||||
import { ModelType } from '../../enums/ModelType';
|
||||
import { navigateToLink } from '../../functions/navigation';
|
||||
import { shortenString } from '../../functions/tables';
|
||||
import { apiUrl } from '../../states/ApiState';
|
||||
import { Thumbnail } from '../images/Thumbnail';
|
||||
import { RenderBuildItem, RenderBuildLine, RenderBuildOrder } from './Build';
|
||||
import {
|
||||
@ -24,7 +25,6 @@ import {
|
||||
RenderProjectCode,
|
||||
RenderSelectionList
|
||||
} from './Generic';
|
||||
import { ModelInformationDict } from './ModelType';
|
||||
import {
|
||||
RenderPurchaseOrder,
|
||||
RenderReturnOrder,
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { Loader } from '@mantine/core';
|
||||
import { useMemo, useState } from 'react';
|
||||
|
||||
import type { ModelType } from '@lib/enums/ModelType';
|
||||
import { api } from '../../App';
|
||||
import type { ModelType } from '../../enums/ModelType';
|
||||
import { RenderInstance } from './Instance';
|
||||
|
||||
/**
|
||||
|
@ -1,283 +1,8 @@
|
||||
import { t } from '@lingui/core/macro';
|
||||
|
||||
import { ApiEndpoints } from '../../enums/ApiEndpoints';
|
||||
import type { ModelType } from '../../enums/ModelType';
|
||||
import type { InvenTreeIconType } from '../../functions/icons';
|
||||
|
||||
export interface ModelInformationInterface {
|
||||
label: string;
|
||||
label_multiple: string;
|
||||
url_overview?: string;
|
||||
url_detail?: string;
|
||||
api_endpoint: ApiEndpoints;
|
||||
admin_url?: string;
|
||||
icon: InvenTreeIconType;
|
||||
}
|
||||
|
||||
export interface TranslatableModelInformationInterface
|
||||
extends Omit<ModelInformationInterface, 'label' | 'label_multiple'> {
|
||||
label: () => string;
|
||||
label_multiple: () => string;
|
||||
}
|
||||
|
||||
export type ModelDict = {
|
||||
[key in keyof typeof ModelType]: TranslatableModelInformationInterface;
|
||||
};
|
||||
|
||||
export const ModelInformationDict: ModelDict = {
|
||||
part: {
|
||||
label: () => t`Part`,
|
||||
label_multiple: () => t`Parts`,
|
||||
url_overview: '/part/category/index/parts',
|
||||
url_detail: '/part/:pk/',
|
||||
api_endpoint: ApiEndpoints.part_list,
|
||||
admin_url: '/part/part/',
|
||||
icon: 'part'
|
||||
},
|
||||
partparametertemplate: {
|
||||
label: () => t`Part Parameter Template`,
|
||||
label_multiple: () => t`Part Parameter Templates`,
|
||||
url_detail: '/partparametertemplate/:pk/',
|
||||
api_endpoint: ApiEndpoints.part_parameter_template_list,
|
||||
icon: 'test_templates'
|
||||
},
|
||||
parttesttemplate: {
|
||||
label: () => t`Part Test Template`,
|
||||
label_multiple: () => t`Part Test Templates`,
|
||||
url_detail: '/parttesttemplate/:pk/',
|
||||
api_endpoint: ApiEndpoints.part_test_template_list,
|
||||
icon: 'test'
|
||||
},
|
||||
supplierpart: {
|
||||
label: () => t`Supplier Part`,
|
||||
label_multiple: () => t`Supplier Parts`,
|
||||
url_overview: '/purchasing/index/supplier-parts',
|
||||
url_detail: '/purchasing/supplier-part/:pk/',
|
||||
api_endpoint: ApiEndpoints.supplier_part_list,
|
||||
admin_url: '/company/supplierpart/',
|
||||
icon: 'supplier_part'
|
||||
},
|
||||
manufacturerpart: {
|
||||
label: () => t`Manufacturer Part`,
|
||||
label_multiple: () => t`Manufacturer Parts`,
|
||||
url_overview: '/purchasing/index/manufacturer-parts',
|
||||
url_detail: '/purchasing/manufacturer-part/:pk/',
|
||||
api_endpoint: ApiEndpoints.manufacturer_part_list,
|
||||
admin_url: '/company/manufacturerpart/',
|
||||
icon: 'manufacturers'
|
||||
},
|
||||
partcategory: {
|
||||
label: () => t`Part Category`,
|
||||
label_multiple: () => t`Part Categories`,
|
||||
url_overview: '/part/category/parts/subcategories',
|
||||
url_detail: '/part/category/:pk/',
|
||||
api_endpoint: ApiEndpoints.category_list,
|
||||
admin_url: '/part/partcategory/',
|
||||
icon: 'category'
|
||||
},
|
||||
stockitem: {
|
||||
label: () => t`Stock Item`,
|
||||
label_multiple: () => t`Stock Items`,
|
||||
url_overview: '/stock/location/index/stock-items',
|
||||
url_detail: '/stock/item/:pk/',
|
||||
api_endpoint: ApiEndpoints.stock_item_list,
|
||||
admin_url: '/stock/stockitem/',
|
||||
icon: 'stock'
|
||||
},
|
||||
stocklocation: {
|
||||
label: () => t`Stock Location`,
|
||||
label_multiple: () => t`Stock Locations`,
|
||||
url_overview: '/stock/location',
|
||||
url_detail: '/stock/location/:pk/',
|
||||
api_endpoint: ApiEndpoints.stock_location_list,
|
||||
admin_url: '/stock/stocklocation/',
|
||||
icon: 'location'
|
||||
},
|
||||
stocklocationtype: {
|
||||
label: () => t`Stock Location Type`,
|
||||
label_multiple: () => t`Stock Location Types`,
|
||||
api_endpoint: ApiEndpoints.stock_location_type_list,
|
||||
icon: 'location'
|
||||
},
|
||||
stockhistory: {
|
||||
label: () => t`Stock History`,
|
||||
label_multiple: () => t`Stock Histories`,
|
||||
api_endpoint: ApiEndpoints.stock_tracking_list,
|
||||
icon: 'history'
|
||||
},
|
||||
build: {
|
||||
label: () => t`Build`,
|
||||
label_multiple: () => t`Builds`,
|
||||
url_overview: '/manufacturing/index/buildorders/',
|
||||
url_detail: '/manufacturing/build-order/:pk/',
|
||||
api_endpoint: ApiEndpoints.build_order_list,
|
||||
admin_url: '/build/build/',
|
||||
icon: 'build_order'
|
||||
},
|
||||
buildline: {
|
||||
label: () => t`Build Line`,
|
||||
label_multiple: () => t`Build Lines`,
|
||||
url_overview: '/build/line',
|
||||
url_detail: '/build/line/:pk/',
|
||||
api_endpoint: ApiEndpoints.build_line_list,
|
||||
icon: 'build_order'
|
||||
},
|
||||
builditem: {
|
||||
label: () => t`Build Item`,
|
||||
label_multiple: () => t`Build Items`,
|
||||
api_endpoint: ApiEndpoints.build_item_list,
|
||||
icon: 'build_order'
|
||||
},
|
||||
company: {
|
||||
label: () => t`Company`,
|
||||
label_multiple: () => t`Companies`,
|
||||
url_detail: '/company/:pk/',
|
||||
api_endpoint: ApiEndpoints.company_list,
|
||||
admin_url: '/company/company/',
|
||||
icon: 'building'
|
||||
},
|
||||
projectcode: {
|
||||
label: () => t`Project Code`,
|
||||
label_multiple: () => t`Project Codes`,
|
||||
url_detail: '/project-code/:pk/',
|
||||
api_endpoint: ApiEndpoints.project_code_list,
|
||||
icon: 'list_details'
|
||||
},
|
||||
purchaseorder: {
|
||||
label: () => t`Purchase Order`,
|
||||
label_multiple: () => t`Purchase Orders`,
|
||||
url_overview: '/purchasing/index/purchaseorders',
|
||||
url_detail: '/purchasing/purchase-order/:pk/',
|
||||
api_endpoint: ApiEndpoints.purchase_order_list,
|
||||
admin_url: '/order/purchaseorder/',
|
||||
icon: 'purchase_orders'
|
||||
},
|
||||
purchaseorderlineitem: {
|
||||
label: () => t`Purchase Order Line`,
|
||||
label_multiple: () => t`Purchase Order Lines`,
|
||||
api_endpoint: ApiEndpoints.purchase_order_line_list,
|
||||
icon: 'purchase_orders'
|
||||
},
|
||||
salesorder: {
|
||||
label: () => t`Sales Order`,
|
||||
label_multiple: () => t`Sales Orders`,
|
||||
url_overview: '/sales/index/salesorders',
|
||||
url_detail: '/sales/sales-order/:pk/',
|
||||
api_endpoint: ApiEndpoints.sales_order_list,
|
||||
admin_url: '/order/salesorder/',
|
||||
icon: 'sales_orders'
|
||||
},
|
||||
salesordershipment: {
|
||||
label: () => t`Sales Order Shipment`,
|
||||
label_multiple: () => t`Sales Order Shipments`,
|
||||
url_detail: '/sales/shipment/:pk/',
|
||||
api_endpoint: ApiEndpoints.sales_order_shipment_list,
|
||||
icon: 'sales_orders'
|
||||
},
|
||||
returnorder: {
|
||||
label: () => t`Return Order`,
|
||||
label_multiple: () => t`Return Orders`,
|
||||
url_overview: '/sales/index/returnorders',
|
||||
url_detail: '/sales/return-order/:pk/',
|
||||
api_endpoint: ApiEndpoints.return_order_list,
|
||||
admin_url: '/order/returnorder/',
|
||||
icon: 'return_orders'
|
||||
},
|
||||
returnorderlineitem: {
|
||||
label: () => t`Return Order Line Item`,
|
||||
label_multiple: () => t`Return Order Line Items`,
|
||||
api_endpoint: ApiEndpoints.return_order_line_list,
|
||||
icon: 'return_orders'
|
||||
},
|
||||
address: {
|
||||
label: () => t`Address`,
|
||||
label_multiple: () => t`Addresses`,
|
||||
url_detail: '/address/:pk/',
|
||||
api_endpoint: ApiEndpoints.address_list,
|
||||
icon: 'address'
|
||||
},
|
||||
contact: {
|
||||
label: () => t`Contact`,
|
||||
label_multiple: () => t`Contacts`,
|
||||
url_detail: '/contact/:pk/',
|
||||
api_endpoint: ApiEndpoints.contact_list,
|
||||
icon: 'group'
|
||||
},
|
||||
owner: {
|
||||
label: () => t`Owner`,
|
||||
label_multiple: () => t`Owners`,
|
||||
url_detail: '/owner/:pk/',
|
||||
api_endpoint: ApiEndpoints.owner_list,
|
||||
icon: 'group'
|
||||
},
|
||||
user: {
|
||||
label: () => t`User`,
|
||||
label_multiple: () => t`Users`,
|
||||
url_detail: '/core/user/:pk/',
|
||||
api_endpoint: ApiEndpoints.user_list,
|
||||
icon: 'user'
|
||||
},
|
||||
group: {
|
||||
label: () => t`Group`,
|
||||
label_multiple: () => t`Groups`,
|
||||
url_detail: '/core/group/:pk/',
|
||||
api_endpoint: ApiEndpoints.group_list,
|
||||
admin_url: '/auth/group/',
|
||||
icon: 'group'
|
||||
},
|
||||
importsession: {
|
||||
label: () => t`Import Session`,
|
||||
label_multiple: () => t`Import Sessions`,
|
||||
url_overview: '/settings/admin/import',
|
||||
url_detail: '/import/:pk/',
|
||||
api_endpoint: ApiEndpoints.import_session_list,
|
||||
icon: 'import'
|
||||
},
|
||||
labeltemplate: {
|
||||
label: () => t`Label Template`,
|
||||
label_multiple: () => t`Label Templates`,
|
||||
url_overview: '/settings/admin/labels',
|
||||
url_detail: '/settings/admin/labels/:pk/',
|
||||
api_endpoint: ApiEndpoints.label_list,
|
||||
icon: 'labels'
|
||||
},
|
||||
reporttemplate: {
|
||||
label: () => t`Report Template`,
|
||||
label_multiple: () => t`Report Templates`,
|
||||
url_overview: '/settings/admin/reports',
|
||||
url_detail: '/settings/admin/reports/:pk/',
|
||||
api_endpoint: ApiEndpoints.report_list,
|
||||
icon: 'reports'
|
||||
},
|
||||
pluginconfig: {
|
||||
label: () => t`Plugin Configuration`,
|
||||
label_multiple: () => t`Plugin Configurations`,
|
||||
url_overview: '/settings/admin/plugin',
|
||||
url_detail: '/settings/admin/plugin/:pk/',
|
||||
api_endpoint: ApiEndpoints.plugin_list,
|
||||
icon: 'plugin'
|
||||
},
|
||||
contenttype: {
|
||||
label: () => t`Content Type`,
|
||||
label_multiple: () => t`Content Types`,
|
||||
api_endpoint: ApiEndpoints.content_type_list,
|
||||
icon: 'list_details'
|
||||
},
|
||||
selectionlist: {
|
||||
label: () => t`Selection List`,
|
||||
label_multiple: () => t`Selection Lists`,
|
||||
api_endpoint: ApiEndpoints.selectionlist_list,
|
||||
icon: 'list_details'
|
||||
},
|
||||
error: {
|
||||
label: () => t`Error`,
|
||||
label_multiple: () => t`Errors`,
|
||||
api_endpoint: ApiEndpoints.error_report_list,
|
||||
url_overview: '/settings/admin/errors',
|
||||
url_detail: '/settings/admin/errors/:pk/',
|
||||
icon: 'exclamation'
|
||||
}
|
||||
};
|
||||
import {
|
||||
ModelInformationDict,
|
||||
type ModelInformationInterface
|
||||
} from '@lib/enums/ModelInformation';
|
||||
import type { ModelType } from '@lib/enums/ModelType';
|
||||
|
||||
/*
|
||||
* Extract model definition given the provided type - returns translatable strings for labels as string, not functions
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { t } from '@lingui/core/macro';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
import { ModelType } from '../../enums/ModelType';
|
||||
import { getDetailUrl } from '../../functions/urls';
|
||||
import { ModelType } from '@lib/enums/ModelType';
|
||||
import { getDetailUrl } from '@lib/functions/Navigation';
|
||||
import { type InstanceRenderInterface, RenderInlineModel } from './Instance';
|
||||
import { StatusRenderer } from './StatusRenderer';
|
||||
|
||||
|
@ -2,8 +2,8 @@ import { t } from '@lingui/core/macro';
|
||||
import { Badge } from '@mantine/core';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
import { ModelType } from '../../enums/ModelType';
|
||||
import { getDetailUrl } from '../../functions/urls';
|
||||
import { ModelType } from '@lib/enums/ModelType';
|
||||
import { getDetailUrl } from '@lib/functions/Navigation';
|
||||
import { ApiIcon } from '../items/ApiIcon';
|
||||
import { type InstanceRenderInterface, RenderInlineModel } from './Instance';
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user