mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Model render refactor (#5894)
* Embiggen search drawer * Refactor search drawer queries: - Use proper permission checks * Actually check user settings * Move StatusRenderer * Update renderers - Improve in-line render for different order types * Update stockitem renderere * Remove old renderer functions * Better data handling in UserState * Tweaks for settings pages * "Fix" scanning page - Rendering is a bit broken currently, as the barcode scan does not send back the model data * "Fix" scanning page - Rendering is a bit broken currently, as the barcode scan does not send back the model data - Required refactoring enumerations out into separate files - Some strange race condition / import loop was happening * Fix incorrect imports * Fixing hover card - Use unique key * fixes * Fix urls.md * More udpates * Fix unused import
This commit is contained in:
parent
9e2da947a9
commit
e6db817c8f
@ -18,6 +18,7 @@ class MyUrlsPlugin(UrlsMixin, InvenTreePlugin):
|
||||
]
|
||||
```
|
||||
|
||||
|
||||
The URLs get exposed under `/plugin/{plugin.slug}/*` and get exposed to the template engine with the prefix `plugin:{plugin.slug}:` (for usage with the [url tag](https://docs.djangoproject.com/en/stable/ref/templates/builtins/#url)).
|
||||
|
||||
!!! info "Note"
|
||||
|
@ -3,7 +3,8 @@ import { useQuery } from '@tanstack/react-query';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { api } from '../App';
|
||||
import { ApiPaths, apiUrl } from '../states/ApiState';
|
||||
import { ApiPaths } from '../enums/ApiEndpoints';
|
||||
import { apiUrl } from '../states/ApiState';
|
||||
import { StatisticItem } from './items/DashboardItem';
|
||||
import { ErrorItem } from './items/ErrorItem';
|
||||
|
||||
|
@ -4,6 +4,7 @@ import { ReactNode } from 'react';
|
||||
import { notYetImplemented } from '../../functions/notifications';
|
||||
|
||||
export type ActionButtonProps = {
|
||||
key?: string;
|
||||
icon?: ReactNode;
|
||||
text?: string;
|
||||
color?: string;
|
||||
@ -22,12 +23,13 @@ export function ActionButton(props: ActionButtonProps) {
|
||||
return (
|
||||
!props.hidden && (
|
||||
<Tooltip
|
||||
key={props.text ?? props.tooltip}
|
||||
key={`tooltip-${props.key}`}
|
||||
disabled={!props.tooltip && !props.text}
|
||||
label={props.tooltip ?? props.text}
|
||||
position="left"
|
||||
>
|
||||
<ActionIcon
|
||||
key={`action-icon-${props.key}`}
|
||||
disabled={props.disabled}
|
||||
radius="xs"
|
||||
color={props.color}
|
||||
|
@ -9,9 +9,9 @@ import { useEffect, useMemo } from 'react';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { api, queryClient } from '../../App';
|
||||
import { ApiPaths } from '../../enums/ApiEndpoints';
|
||||
import { constructFormUrl } from '../../functions/forms';
|
||||
import { invalidResponse } from '../../functions/notifications';
|
||||
import { ApiPaths } from '../../states/ApiState';
|
||||
import { ApiFormField, ApiFormFieldSet } from './fields/ApiFormField';
|
||||
|
||||
/**
|
||||
|
@ -14,7 +14,7 @@ import { IconX } from '@tabler/icons-react';
|
||||
import { ReactNode } from 'react';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { ModelType } from '../../render/ModelType';
|
||||
import { ModelType } from '../../../enums/ModelType';
|
||||
import { ApiFormProps } from '../ApiForm';
|
||||
import { ChoiceField } from './ChoiceField';
|
||||
import { RelatedModelField } from './RelatedModelField';
|
||||
|
@ -81,7 +81,7 @@ export function BarcodeActionDropdown({
|
||||
}) {
|
||||
return (
|
||||
<ActionDropdown
|
||||
key="barcode"
|
||||
key="barcode-actions"
|
||||
tooltip={t`Barcode Actions`}
|
||||
icon={<IconQrcode />}
|
||||
actions={actions}
|
||||
|
@ -4,7 +4,8 @@ import { ContextModalProps } from '@mantine/modals';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { api } from '../../App';
|
||||
import { ApiPaths, apiUrl, useServerApiState } from '../../states/ApiState';
|
||||
import { ApiPaths } from '../../enums/ApiEndpoints';
|
||||
import { apiUrl, useServerApiState } from '../../states/ApiState';
|
||||
import { useLocalState } from '../../states/LocalState';
|
||||
import { useUserState } from '../../states/UserState';
|
||||
import { CopyButton } from '../items/CopyButton';
|
||||
|
@ -23,7 +23,8 @@ import { Html5QrcodeResult } from 'html5-qrcode/core';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { api } from '../../App';
|
||||
import { ApiPaths, apiUrl } from '../../states/ApiState';
|
||||
import { ApiPaths } from '../../enums/ApiEndpoints';
|
||||
import { apiUrl } from '../../states/ApiState';
|
||||
|
||||
export function QrCodeModal({
|
||||
context,
|
||||
|
@ -7,8 +7,9 @@ import { useNavigate, useParams } from 'react-router-dom';
|
||||
|
||||
import { api } from '../../App';
|
||||
import { navTabs as mainNavTabs } from '../../defaults/links';
|
||||
import { ApiPaths } from '../../enums/ApiEndpoints';
|
||||
import { InvenTreeStyle } from '../../globalStyle';
|
||||
import { ApiPaths, apiUrl } from '../../states/ApiState';
|
||||
import { apiUrl } from '../../states/ApiState';
|
||||
import { ScanButton } from '../items/ScanButton';
|
||||
import { MainMenu } from './MainMenu';
|
||||
import { NavHoverMenu } from './NavHoverMenu';
|
||||
|
@ -14,7 +14,8 @@ import { useQuery } from '@tanstack/react-query';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { api } from '../../App';
|
||||
import { ApiPaths, apiUrl } from '../../states/ApiState';
|
||||
import { ApiPaths } from '../../enums/ApiEndpoints';
|
||||
import { apiUrl } from '../../states/ApiState';
|
||||
import { StylishText } from '../items/StylishText';
|
||||
|
||||
/**
|
||||
|
@ -58,7 +58,7 @@ export function PageDetail({
|
||||
{detail}
|
||||
<Space />
|
||||
{actions && (
|
||||
<Group spacing={5} position="right">
|
||||
<Group key="page-actions" spacing={5} position="right">
|
||||
{actions}
|
||||
</Group>
|
||||
)}
|
||||
|
@ -6,7 +6,8 @@ import { useQuery } from '@tanstack/react-query';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { api } from '../../App';
|
||||
import { ApiPaths, apiUrl } from '../../states/ApiState';
|
||||
import { ApiPaths } from '../../enums/ApiEndpoints';
|
||||
import { apiUrl } from '../../states/ApiState';
|
||||
import { StylishText } from '../items/StylishText';
|
||||
|
||||
export function PartCategoryTree({
|
||||
|
@ -26,139 +26,27 @@ import {
|
||||
IconX
|
||||
} from '@tabler/icons-react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { api } from '../../App';
|
||||
import { ApiPaths, apiUrl } from '../../states/ApiState';
|
||||
import { ApiPaths } from '../../enums/ApiEndpoints';
|
||||
import { ModelType } from '../../enums/ModelType';
|
||||
import { UserRoles } from '../../enums/Roles';
|
||||
import { apiUrl } from '../../states/ApiState';
|
||||
import { useUserSettingsState } from '../../states/SettingsState';
|
||||
import { useUserState } from '../../states/UserState';
|
||||
import { RenderInstance } from '../render/Instance';
|
||||
import { ModelInformationDict, ModelType } from '../render/ModelType';
|
||||
import { ModelInformationDict } from '../render/ModelType';
|
||||
|
||||
// Define type for handling individual search queries
|
||||
type SearchQuery = {
|
||||
name: ModelType;
|
||||
model: ModelType;
|
||||
enabled: boolean;
|
||||
parameters: any;
|
||||
results?: any;
|
||||
};
|
||||
|
||||
// Placeholder function for permissions checks (will be replaced with a proper implementation)
|
||||
function permissionCheck(permission: string) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Placeholder function for settings checks (will be replaced with a proper implementation)
|
||||
function settingsCheck(setting: string) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Build a list of search queries based on user permissions
|
||||
*/
|
||||
function buildSearchQueries(): SearchQuery[] {
|
||||
return [
|
||||
{
|
||||
name: ModelType.part,
|
||||
parameters: {},
|
||||
enabled:
|
||||
permissionCheck('part.view') &&
|
||||
settingsCheck('SEARCH_PREVIEW_SHOW_PARTS')
|
||||
},
|
||||
{
|
||||
name: ModelType.supplierpart,
|
||||
parameters: {
|
||||
part_detail: true,
|
||||
supplier_detail: true,
|
||||
manufacturer_detail: true
|
||||
},
|
||||
enabled:
|
||||
permissionCheck('part.view') &&
|
||||
permissionCheck('purchase_order.view') &&
|
||||
settingsCheck('SEARCH_PREVIEW_SHOW_SUPPLIER_PARTS')
|
||||
},
|
||||
{
|
||||
name: ModelType.manufacturerpart,
|
||||
parameters: {
|
||||
part_detail: true,
|
||||
supplier_detail: true,
|
||||
manufacturer_detail: true
|
||||
},
|
||||
enabled:
|
||||
permissionCheck('part.view') &&
|
||||
permissionCheck('purchase_order.view') &&
|
||||
settingsCheck('SEARCH_PREVIEW_SHOW_MANUFACTURER_PARTS')
|
||||
},
|
||||
{
|
||||
name: ModelType.partcategory,
|
||||
parameters: {},
|
||||
enabled:
|
||||
permissionCheck('part_category.view') &&
|
||||
settingsCheck('SEARCH_PREVIEW_SHOW_CATEGORIES')
|
||||
},
|
||||
{
|
||||
name: ModelType.stockitem,
|
||||
parameters: {
|
||||
part_detail: true,
|
||||
location_detail: true
|
||||
},
|
||||
enabled:
|
||||
permissionCheck('stock.view') &&
|
||||
settingsCheck('SEARCH_PREVIEW_SHOW_STOCK')
|
||||
},
|
||||
{
|
||||
name: ModelType.stocklocation,
|
||||
parameters: {},
|
||||
enabled:
|
||||
permissionCheck('stock_location.view') &&
|
||||
settingsCheck('SEARCH_PREVIEW_SHOW_LOCATIONS')
|
||||
},
|
||||
{
|
||||
name: ModelType.build,
|
||||
parameters: {
|
||||
part_detail: true
|
||||
},
|
||||
enabled:
|
||||
permissionCheck('build.view') &&
|
||||
settingsCheck('SEARCH_PREVIEW_SHOW_BUILD_ORDERS')
|
||||
},
|
||||
{
|
||||
name: ModelType.company,
|
||||
parameters: {},
|
||||
enabled:
|
||||
(permissionCheck('sales_order.view') ||
|
||||
permissionCheck('purchase_order.view')) &&
|
||||
settingsCheck('SEARCH_PREVIEW_SHOW_COMPANIES')
|
||||
},
|
||||
{
|
||||
name: ModelType.purchaseorder,
|
||||
parameters: {
|
||||
supplier_detail: true
|
||||
},
|
||||
enabled:
|
||||
permissionCheck('purchase_order.view') &&
|
||||
settingsCheck(`SEARCH_PREVIEW_SHOW_PURCHASE_ORDERS`)
|
||||
},
|
||||
{
|
||||
name: ModelType.salesorder,
|
||||
parameters: {
|
||||
customer_detail: true
|
||||
},
|
||||
enabled:
|
||||
permissionCheck('sales_order.view') &&
|
||||
settingsCheck(`SEARCH_PREVIEW_SHOW_SALES_ORDERS`)
|
||||
},
|
||||
{
|
||||
name: ModelType.returnorder,
|
||||
parameters: {
|
||||
customer_detail: true
|
||||
},
|
||||
enabled:
|
||||
permissionCheck('return_order.view') &&
|
||||
settingsCheck(`SEARCH_PREVIEW_SHOW_RETURN_ORDERS`)
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/*
|
||||
* Render the results for a single search query
|
||||
*/
|
||||
@ -175,10 +63,11 @@ function QueryResultGroup({
|
||||
return null;
|
||||
}
|
||||
|
||||
const model = ModelInformationDict[query.name];
|
||||
const model = ModelInformationDict[query.model];
|
||||
|
||||
return (
|
||||
<Paper shadow="sm" radius="xs" p="md">
|
||||
<Stack key={query.name}>
|
||||
<Paper shadow="sm" radius="xs" p="md" key={`paper-${query.model}`}>
|
||||
<Stack key={`stack-${query.model}`}>
|
||||
<Group position="apart" noWrap={true}>
|
||||
<Group position="left" spacing={5} noWrap={true}>
|
||||
<Text size="lg">{model.label_multiple}</Text>
|
||||
@ -193,7 +82,7 @@ function QueryResultGroup({
|
||||
color="red"
|
||||
variant="transparent"
|
||||
radius="xs"
|
||||
onClick={() => onRemove(query.name)}
|
||||
onClick={() => onRemove(query.model)}
|
||||
>
|
||||
<IconX />
|
||||
</ActionIcon>
|
||||
@ -201,11 +90,11 @@ function QueryResultGroup({
|
||||
<Divider />
|
||||
<Stack>
|
||||
{query.results.results.map((result: any) => (
|
||||
<Anchor onClick={() => onResultClick(query.name, result.pk)}>
|
||||
<Anchor onClick={() => onResultClick(query.model, result.pk)}>
|
||||
<RenderInstance
|
||||
key={`${query.name}-${result.pk}`}
|
||||
key={`${query.model}-${result.pk}`}
|
||||
instance={result}
|
||||
model={query.name}
|
||||
model={query.model}
|
||||
/>
|
||||
</Anchor>
|
||||
))}
|
||||
@ -233,10 +122,116 @@ export function SearchDrawer({
|
||||
const [searchRegex, setSearchRegex] = useState<boolean>(false);
|
||||
const [searchWhole, setSearchWhole] = useState<boolean>(false);
|
||||
|
||||
const user = useUserState();
|
||||
const userSettings = useUserSettingsState();
|
||||
|
||||
// Build out search queries based on user permissions and preferences
|
||||
const searchQueryList: SearchQuery[] = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
model: ModelType.part,
|
||||
parameters: {},
|
||||
enabled:
|
||||
user.hasViewRole(UserRoles.part) &&
|
||||
userSettings.isSet('SEARCH_PREVIEW_SHOW_PARTS')
|
||||
},
|
||||
{
|
||||
model: ModelType.supplierpart,
|
||||
parameters: {
|
||||
part_detail: true,
|
||||
supplier_detail: true,
|
||||
manufacturer_detail: true
|
||||
},
|
||||
enabled:
|
||||
user.hasViewRole(UserRoles.part) &&
|
||||
user.hasViewRole(UserRoles.purchase_order) &&
|
||||
userSettings.isSet('SEARCH_PREVIEW_SHOW_SUPPLIER_PARTS')
|
||||
},
|
||||
{
|
||||
model: ModelType.manufacturerpart,
|
||||
parameters: {
|
||||
part_detail: true,
|
||||
supplier_detail: true,
|
||||
manufacturer_detail: true
|
||||
},
|
||||
enabled:
|
||||
user.hasViewRole(UserRoles.part) &&
|
||||
user.hasViewRole(UserRoles.purchase_order) &&
|
||||
userSettings.isSet('SEARCH_PREVIEW_SHOW_MANUFACTURER_PARTS')
|
||||
},
|
||||
{
|
||||
model: ModelType.partcategory,
|
||||
parameters: {},
|
||||
enabled:
|
||||
user.hasViewRole(UserRoles.part_category) &&
|
||||
userSettings.isSet('SEARCH_PREVIEW_SHOW_CATEGORIES')
|
||||
},
|
||||
{
|
||||
model: ModelType.stockitem,
|
||||
parameters: {
|
||||
part_detail: true,
|
||||
location_detail: true
|
||||
},
|
||||
enabled:
|
||||
user.hasViewRole(UserRoles.stock) &&
|
||||
userSettings.isSet('SEARCH_PREVIEW_SHOW_STOCK')
|
||||
},
|
||||
{
|
||||
model: ModelType.stocklocation,
|
||||
parameters: {},
|
||||
enabled:
|
||||
user.hasViewRole(UserRoles.stock_location) &&
|
||||
userSettings.isSet('SEARCH_PREVIEW_SHOW_LOCATIONS')
|
||||
},
|
||||
{
|
||||
model: ModelType.build,
|
||||
parameters: {
|
||||
part_detail: true
|
||||
},
|
||||
enabled:
|
||||
user.hasViewRole(UserRoles.build) &&
|
||||
userSettings.isSet('SEARCH_PREVIEW_SHOW_BUILD_ORDERS')
|
||||
},
|
||||
{
|
||||
model: ModelType.company,
|
||||
parameters: {},
|
||||
enabled:
|
||||
(user.hasViewRole(UserRoles.sales_order) ||
|
||||
user.hasViewRole(UserRoles.purchase_order)) &&
|
||||
userSettings.isSet('SEARCH_PREVIEW_SHOW_COMPANIES')
|
||||
},
|
||||
{
|
||||
model: ModelType.purchaseorder,
|
||||
parameters: {
|
||||
supplier_detail: true
|
||||
},
|
||||
enabled:
|
||||
user.hasViewRole(UserRoles.purchase_order) &&
|
||||
userSettings.isSet(`SEARCH_PREVIEW_SHOW_PURCHASE_ORDERS`)
|
||||
},
|
||||
{
|
||||
model: ModelType.salesorder,
|
||||
parameters: {
|
||||
customer_detail: true
|
||||
},
|
||||
enabled:
|
||||
user.hasViewRole(UserRoles.sales_order) &&
|
||||
userSettings.isSet(`SEARCH_PREVIEW_SHOW_SALES_ORDERS`)
|
||||
},
|
||||
{
|
||||
model: ModelType.returnorder,
|
||||
parameters: {
|
||||
customer_detail: true
|
||||
},
|
||||
enabled:
|
||||
user.hasViewRole(UserRoles.return_order) &&
|
||||
userSettings.isSet(`SEARCH_PREVIEW_SHOW_RETURN_ORDERS`)
|
||||
}
|
||||
];
|
||||
}, [user, userSettings]);
|
||||
|
||||
// Construct a list of search queries based on user permissions
|
||||
const searchQueries: SearchQuery[] = buildSearchQueries().filter(
|
||||
(q) => q.enabled
|
||||
);
|
||||
const searchQueries: SearchQuery[] = searchQueryList.filter((q) => q.enabled);
|
||||
|
||||
// Re-fetch data whenever the search term is updated
|
||||
useEffect(() => {
|
||||
@ -261,7 +256,7 @@ export function SearchDrawer({
|
||||
|
||||
// Add in custom query parameters
|
||||
searchQueries.forEach((query) => {
|
||||
params[query.name] = query.parameters;
|
||||
params[query.model] = query.parameters;
|
||||
});
|
||||
|
||||
return api
|
||||
@ -289,11 +284,11 @@ export function SearchDrawer({
|
||||
useEffect(() => {
|
||||
if (searchQuery.data) {
|
||||
let queries = searchQueries.filter(
|
||||
(query) => query.name in searchQuery.data
|
||||
(query) => query.model in searchQuery.data
|
||||
);
|
||||
|
||||
for (let key in searchQuery.data) {
|
||||
let query = queries.find((q) => q.name == key);
|
||||
let query = queries.find((q) => q.model == key);
|
||||
if (query) {
|
||||
query.results = searchQuery.data[key];
|
||||
}
|
||||
@ -310,7 +305,7 @@ export function SearchDrawer({
|
||||
|
||||
// Callback to remove a set of results from the list
|
||||
function removeResults(query: ModelType) {
|
||||
setQueryResults(queryResults.filter((q) => q.name != query));
|
||||
setQueryResults(queryResults.filter((q) => q.model != query));
|
||||
}
|
||||
|
||||
// Callback when the drawer is closed
|
||||
@ -332,7 +327,7 @@ export function SearchDrawer({
|
||||
return (
|
||||
<Drawer
|
||||
opened={opened}
|
||||
size="md"
|
||||
size="xl"
|
||||
onClose={closeDrawer}
|
||||
position="right"
|
||||
withCloseButton={false}
|
||||
|
@ -1,8 +1,18 @@
|
||||
import { Anchor, Group, Stack, Text, Title } from '@mantine/core';
|
||||
import {
|
||||
Anchor,
|
||||
Button,
|
||||
Group,
|
||||
Paper,
|
||||
Space,
|
||||
Stack,
|
||||
Text
|
||||
} from '@mantine/core';
|
||||
import { IconSwitch } from '@tabler/icons-react';
|
||||
import { ReactNode } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { StylishText } from '../items/StylishText';
|
||||
|
||||
/**
|
||||
* Construct a settings page header with interlinks to one other settings page
|
||||
*/
|
||||
@ -14,28 +24,35 @@ export function SettingsHeader({
|
||||
switch_text,
|
||||
switch_link
|
||||
}: {
|
||||
title: string | ReactNode;
|
||||
title: string;
|
||||
shorthand?: string;
|
||||
subtitle: string | ReactNode;
|
||||
subtitle?: string;
|
||||
switch_condition?: boolean;
|
||||
switch_text?: string | ReactNode;
|
||||
switch_link?: string;
|
||||
}) {
|
||||
return (
|
||||
<Stack spacing="0" ml={'sm'}>
|
||||
<Group>
|
||||
<Title order={3}>{title}</Title>
|
||||
{shorthand && <Text c="dimmed">({shorthand})</Text>}
|
||||
<Paper shadow="xs" radius="xs" p="xs">
|
||||
<Group position="apart">
|
||||
<Stack spacing="xs">
|
||||
<Group position="left" spacing="xs">
|
||||
<StylishText size="xl">{title}</StylishText>
|
||||
<Text size="sm">{shorthand}</Text>
|
||||
</Group>
|
||||
<Group>
|
||||
<Text c="dimmed">{subtitle}</Text>
|
||||
<Text italic>{subtitle}</Text>
|
||||
</Stack>
|
||||
<Space />
|
||||
{switch_text && switch_link && switch_condition && (
|
||||
<Anchor component={Link} to={switch_link}>
|
||||
<IconSwitch size={14} />
|
||||
{switch_text}
|
||||
<Button variant="outline">
|
||||
<Group spacing="sm">
|
||||
<IconSwitch size={18} />
|
||||
<Text>{switch_text}</Text>
|
||||
</Group>
|
||||
</Button>
|
||||
</Anchor>
|
||||
)}
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
|
@ -6,7 +6,8 @@ import { useQuery } from '@tanstack/react-query';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { api } from '../../App';
|
||||
import { ApiPaths, apiUrl } from '../../states/ApiState';
|
||||
import { ApiPaths } from '../../enums/ApiEndpoints';
|
||||
import { apiUrl } from '../../states/ApiState';
|
||||
import { StylishText } from '../items/StylishText';
|
||||
|
||||
export function StockLocationTree({
|
||||
|
@ -1,6 +1,8 @@
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
import { ModelType } from '../../enums/ModelType';
|
||||
import { RenderInlineModel } from './Instance';
|
||||
import { StatusRenderer } from './StatusRenderer';
|
||||
|
||||
/**
|
||||
* Inline rendering of a single BuildOrder instance
|
||||
@ -10,6 +12,10 @@ export function RenderBuildOrder({ instance }: { instance: any }): ReactNode {
|
||||
<RenderInlineModel
|
||||
primary={instance.reference}
|
||||
secondary={instance.title}
|
||||
suffix={StatusRenderer({
|
||||
status: instance.status,
|
||||
type: ModelType.build
|
||||
})}
|
||||
image={instance.part_detail?.thumbnail || instance.part_detail?.image}
|
||||
/>
|
||||
);
|
||||
|
@ -3,6 +3,7 @@ import { Alert, Space } from '@mantine/core';
|
||||
import { Group, Text } from '@mantine/core';
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
import { ModelType } from '../../enums/ModelType';
|
||||
import { Thumbnail } from '../images/Thumbnail';
|
||||
import { RenderBuildOrder } from './Build';
|
||||
import {
|
||||
@ -13,7 +14,6 @@ import {
|
||||
RenderSupplierPart
|
||||
} from './Company';
|
||||
import { RenderProjectCode } from './Generic';
|
||||
import { ModelType } from './ModelType';
|
||||
import {
|
||||
RenderPurchaseOrder,
|
||||
RenderReturnOrder,
|
||||
@ -101,7 +101,7 @@ export function RenderInlineModel({
|
||||
}: {
|
||||
primary: string;
|
||||
secondary?: string;
|
||||
suffix?: string;
|
||||
suffix?: ReactNode;
|
||||
image?: string;
|
||||
labels?: string[];
|
||||
url?: string;
|
||||
|
@ -1,37 +1,18 @@
|
||||
import { t } from '@lingui/macro';
|
||||
|
||||
export enum ModelType {
|
||||
part = 'part',
|
||||
supplierpart = 'supplierpart',
|
||||
manufacturerpart = 'manufacturerpart',
|
||||
partcategory = 'partcategory',
|
||||
partparametertemplate = 'partparametertemplate',
|
||||
projectcode = 'projectcode',
|
||||
stockitem = 'stockitem',
|
||||
stocklocation = 'stocklocation',
|
||||
stockhistory = 'stockhistory',
|
||||
build = 'build',
|
||||
company = 'company',
|
||||
purchaseorder = 'purchaseorder',
|
||||
purchaseorderline = 'purchaseorderline',
|
||||
salesorder = 'salesorder',
|
||||
salesordershipment = 'salesordershipment',
|
||||
returnorder = 'returnorder',
|
||||
address = 'address',
|
||||
contact = 'contact',
|
||||
owner = 'owner',
|
||||
user = 'user'
|
||||
}
|
||||
import { ApiPaths } from '../../enums/ApiEndpoints';
|
||||
import { ModelType } from '../../enums/ModelType';
|
||||
|
||||
interface ModelInformatonInterface {
|
||||
interface ModelInformationInterface {
|
||||
label: string;
|
||||
label_multiple: string;
|
||||
url_overview?: string;
|
||||
url_detail?: string;
|
||||
api_endpoint?: ApiPaths;
|
||||
}
|
||||
|
||||
type ModelDictory = {
|
||||
[key in keyof typeof ModelType]: ModelInformatonInterface;
|
||||
[key in keyof typeof ModelType]: ModelInformationInterface;
|
||||
};
|
||||
|
||||
export const ModelInformationDict: ModelDictory = {
|
||||
@ -39,116 +20,136 @@ export const ModelInformationDict: ModelDictory = {
|
||||
label: t`Part`,
|
||||
label_multiple: t`Parts`,
|
||||
url_overview: '/part',
|
||||
url_detail: '/part/:pk/'
|
||||
url_detail: '/part/:pk/',
|
||||
api_endpoint: ApiPaths.part_list
|
||||
},
|
||||
partparametertemplate: {
|
||||
label: t`Part Parameter Template`,
|
||||
label_multiple: t`Part Parameter Templates`,
|
||||
url_overview: '/partparametertemplate',
|
||||
url_detail: '/partparametertemplate/:pk/'
|
||||
url_detail: '/partparametertemplate/:pk/',
|
||||
api_endpoint: ApiPaths.part_parameter_template_list
|
||||
},
|
||||
supplierpart: {
|
||||
label: t`Supplier Part`,
|
||||
label_multiple: t`Supplier Parts`,
|
||||
url_overview: '/supplierpart',
|
||||
url_detail: '/supplierpart/:pk/'
|
||||
url_detail: '/supplierpart/:pk/',
|
||||
api_endpoint: ApiPaths.supplier_part_list
|
||||
},
|
||||
manufacturerpart: {
|
||||
label: t`Manufacturer Part`,
|
||||
label_multiple: t`Manufacturer Parts`,
|
||||
url_overview: '/manufacturerpart',
|
||||
url_detail: '/manufacturerpart/:pk/'
|
||||
url_detail: '/manufacturerpart/:pk/',
|
||||
api_endpoint: ApiPaths.manufacturer_part_list
|
||||
},
|
||||
partcategory: {
|
||||
label: t`Part Category`,
|
||||
label_multiple: t`Part Categories`,
|
||||
url_overview: '/partcategory',
|
||||
url_detail: '/partcategory/:pk/'
|
||||
url_detail: '/partcategory/:pk/',
|
||||
api_endpoint: ApiPaths.category_list
|
||||
},
|
||||
stockitem: {
|
||||
label: t`Stock Item`,
|
||||
label_multiple: t`Stock Items`,
|
||||
url_overview: '/stockitem',
|
||||
url_detail: '/stockitem/:pk/'
|
||||
url_detail: '/stockitem/:pk/',
|
||||
api_endpoint: ApiPaths.stock_item_list
|
||||
},
|
||||
stocklocation: {
|
||||
label: t`Stock Location`,
|
||||
label_multiple: t`Stock Locations`,
|
||||
url_overview: '/stocklocation',
|
||||
url_detail: '/stocklocation/:pk/'
|
||||
url_detail: '/stocklocation/:pk/',
|
||||
api_endpoint: ApiPaths.stock_location_list
|
||||
},
|
||||
stockhistory: {
|
||||
label: t`Stock History`,
|
||||
label_multiple: t`Stock Histories`
|
||||
label_multiple: t`Stock Histories`,
|
||||
api_endpoint: ApiPaths.stock_tracking_list
|
||||
},
|
||||
build: {
|
||||
label: t`Build`,
|
||||
label_multiple: t`Builds`,
|
||||
url_overview: '/build',
|
||||
url_detail: '/build/:pk/'
|
||||
url_detail: '/build/:pk/',
|
||||
api_endpoint: ApiPaths.build_order_list
|
||||
},
|
||||
company: {
|
||||
label: t`Company`,
|
||||
label_multiple: t`Companies`,
|
||||
url_overview: '/company',
|
||||
url_detail: '/company/:pk/'
|
||||
url_detail: '/company/:pk/',
|
||||
api_endpoint: ApiPaths.company_list
|
||||
},
|
||||
projectcode: {
|
||||
label: t`Project Code`,
|
||||
label_multiple: t`Project Codes`,
|
||||
url_overview: '/project-code',
|
||||
url_detail: '/project-code/:pk/'
|
||||
url_detail: '/project-code/:pk/',
|
||||
api_endpoint: ApiPaths.project_code_list
|
||||
},
|
||||
purchaseorder: {
|
||||
label: t`Purchase Order`,
|
||||
label_multiple: t`Purchase Orders`,
|
||||
url_overview: '/purchaseorder',
|
||||
url_detail: '/purchaseorder/:pk/'
|
||||
url_detail: '/purchaseorder/:pk/',
|
||||
api_endpoint: ApiPaths.purchase_order_list
|
||||
},
|
||||
purchaseorderline: {
|
||||
label: t`Purchase Order Line`,
|
||||
label_multiple: t`Purchase Order Lines`
|
||||
label_multiple: t`Purchase Order Lines`,
|
||||
api_endpoint: ApiPaths.purchase_order_line_list
|
||||
},
|
||||
salesorder: {
|
||||
label: t`Sales Order`,
|
||||
label_multiple: t`Sales Orders`,
|
||||
url_overview: '/salesorder',
|
||||
url_detail: '/salesorder/:pk/'
|
||||
url_detail: '/salesorder/:pk/',
|
||||
api_endpoint: ApiPaths.sales_order_list
|
||||
},
|
||||
salesordershipment: {
|
||||
label: t`Sales Order Shipment`,
|
||||
label_multiple: t`Sales Order Shipments`,
|
||||
url_overview: '/salesordershipment',
|
||||
url_detail: '/salesordershipment/:pk/'
|
||||
url_detail: '/salesordershipment/:pk/',
|
||||
api_endpoint: ApiPaths.sales_order_shipment_list
|
||||
},
|
||||
returnorder: {
|
||||
label: t`Return Order`,
|
||||
label_multiple: t`Return Orders`,
|
||||
url_overview: '/returnorder',
|
||||
url_detail: '/returnorder/:pk/'
|
||||
url_detail: '/returnorder/:pk/',
|
||||
api_endpoint: ApiPaths.return_order_list
|
||||
},
|
||||
address: {
|
||||
label: t`Address`,
|
||||
label_multiple: t`Addresses`,
|
||||
url_overview: '/address',
|
||||
url_detail: '/address/:pk/'
|
||||
url_detail: '/address/:pk/',
|
||||
api_endpoint: ApiPaths.address_list
|
||||
},
|
||||
contact: {
|
||||
label: t`Contact`,
|
||||
label_multiple: t`Contacts`,
|
||||
url_overview: '/contact',
|
||||
url_detail: '/contact/:pk/'
|
||||
url_detail: '/contact/:pk/',
|
||||
api_endpoint: ApiPaths.contact_list
|
||||
},
|
||||
owner: {
|
||||
label: t`Owner`,
|
||||
label_multiple: t`Owners`,
|
||||
url_overview: '/owner',
|
||||
url_detail: '/owner/:pk/'
|
||||
url_detail: '/owner/:pk/',
|
||||
api_endpoint: ApiPaths.owner_list
|
||||
},
|
||||
user: {
|
||||
label: t`User`,
|
||||
label_multiple: t`Users`,
|
||||
url_overview: '/user',
|
||||
url_detail: '/user/:pk/'
|
||||
url_detail: '/user/:pk/',
|
||||
api_endpoint: ApiPaths.user_list
|
||||
}
|
||||
};
|
||||
|
@ -1,7 +1,9 @@
|
||||
import { t } from '@lingui/macro';
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
import { ModelType } from '../../enums/ModelType';
|
||||
import { RenderInlineModel } from './Instance';
|
||||
import { StatusRenderer } from './StatusRenderer';
|
||||
|
||||
/**
|
||||
* Inline rendering of a single PurchaseOrder instance
|
||||
@ -18,6 +20,10 @@ export function RenderPurchaseOrder({
|
||||
<RenderInlineModel
|
||||
primary={instance.reference}
|
||||
secondary={instance.description}
|
||||
suffix={StatusRenderer({
|
||||
status: instance.status,
|
||||
type: ModelType.purchaseorder
|
||||
})}
|
||||
image={supplier.thumnbnail || supplier.image}
|
||||
/>
|
||||
);
|
||||
@ -33,6 +39,10 @@ export function RenderReturnOrder({ instance }: { instance: any }): ReactNode {
|
||||
<RenderInlineModel
|
||||
primary={instance.reference}
|
||||
secondary={instance.description}
|
||||
suffix={StatusRenderer({
|
||||
status: instance.status,
|
||||
type: ModelType.returnorder
|
||||
})}
|
||||
image={customer.thumnbnail || customer.image}
|
||||
/>
|
||||
);
|
||||
@ -50,6 +60,10 @@ export function RenderSalesOrder({ instance }: { instance: any }): ReactNode {
|
||||
<RenderInlineModel
|
||||
primary={instance.reference}
|
||||
secondary={instance.description}
|
||||
suffix={StatusRenderer({
|
||||
status: instance.status,
|
||||
type: ModelType.salesorder
|
||||
})}
|
||||
image={customer.thumnbnail || customer.image}
|
||||
/>
|
||||
);
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { Badge, Center, MantineSize } from '@mantine/core';
|
||||
|
||||
import { colorMap } from '../../defaults/backendMappings';
|
||||
import { ModelType } from '../../enums/ModelType';
|
||||
import { useServerApiState } from '../../states/ApiState';
|
||||
import { ModelType } from '../render/ModelType';
|
||||
|
||||
interface StatusCodeInterface {
|
||||
key: string;
|
@ -1,3 +1,4 @@
|
||||
import { t } from '@lingui/macro';
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
import { RenderInlineModel } from './Instance';
|
||||
@ -19,10 +20,18 @@ export function RenderStockLocation({
|
||||
}
|
||||
|
||||
export function RenderStockItem({ instance }: { instance: any }): ReactNode {
|
||||
let quantity_string = '';
|
||||
|
||||
if (instance?.serial !== null && instance?.serial !== undefined) {
|
||||
quantity_string += t`Serial Number` + `: ${instance.serial}`;
|
||||
} else if (instance?.quantity) {
|
||||
quantity_string = t`Quantity` + `: ${instance.quantity}`;
|
||||
}
|
||||
|
||||
return (
|
||||
<RenderInlineModel
|
||||
primary={instance.part_detail?.full_name}
|
||||
secondary={instance.quantity}
|
||||
suffix={quantity_string}
|
||||
image={instance.part_detail?.thumbnail || instance.part_detail?.image}
|
||||
/>
|
||||
);
|
||||
|
@ -1,31 +0,0 @@
|
||||
import { Group } from '@mantine/core';
|
||||
|
||||
import { ApiPaths } from '../../states/ApiState';
|
||||
import { GeneralRenderer } from './GeneralRenderer';
|
||||
import { PartRenderer } from './PartRenderer';
|
||||
|
||||
export const BuildOrderRenderer = ({ pk }: { pk: string }) => {
|
||||
const DetailRenderer = (data: any) => {
|
||||
return (
|
||||
<Group position="apart">
|
||||
{data?.reference}
|
||||
<small>
|
||||
<PartRenderer
|
||||
pk={data?.part_detail?.pk}
|
||||
data={data?.part_detail}
|
||||
link={true}
|
||||
/>
|
||||
</small>
|
||||
</Group>
|
||||
);
|
||||
};
|
||||
return (
|
||||
<GeneralRenderer
|
||||
api_key={ApiPaths.build_order_list}
|
||||
api_ref="build_order"
|
||||
link={`/build/${pk}`}
|
||||
pk={pk}
|
||||
renderer={DetailRenderer}
|
||||
/>
|
||||
);
|
||||
};
|
@ -1,83 +0,0 @@
|
||||
import { Anchor, Loader } from '@mantine/core';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
|
||||
import { api } from '../../App';
|
||||
import { ApiPaths, apiUrl } from '../../states/ApiState';
|
||||
import { ThumbnailHoverCard } from '../images/Thumbnail';
|
||||
|
||||
export function GeneralRenderer({
|
||||
api_key,
|
||||
api_ref: ref,
|
||||
link,
|
||||
pk,
|
||||
image = true,
|
||||
data = undefined,
|
||||
renderer
|
||||
}: {
|
||||
api_key: ApiPaths;
|
||||
api_ref: string;
|
||||
link: string;
|
||||
pk: string;
|
||||
image?: boolean;
|
||||
data?: any;
|
||||
renderer?: (data: any) => JSX.Element;
|
||||
}) {
|
||||
// check if data was passed - or fetch it
|
||||
if (!data) {
|
||||
const {
|
||||
data: fetched_data,
|
||||
isError,
|
||||
isFetching,
|
||||
isLoading
|
||||
} = useQuery({
|
||||
queryKey: [ref, pk],
|
||||
queryFn: () => {
|
||||
return api
|
||||
.get(apiUrl(api_key, pk))
|
||||
.then((res) => res.data)
|
||||
.catch(() => {
|
||||
{
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Loading section
|
||||
if (isError) {
|
||||
return <div>Something went wrong...</div>;
|
||||
}
|
||||
if (isFetching || isLoading) {
|
||||
return <Loader />;
|
||||
}
|
||||
data = fetched_data;
|
||||
}
|
||||
|
||||
// Renderers
|
||||
let content = undefined;
|
||||
// Specific renderer was passed
|
||||
if (renderer) content = renderer(data);
|
||||
|
||||
// No image and no content no default renderer
|
||||
if (image === false && !content) content = data.name;
|
||||
|
||||
// Wrap in link if link was passed
|
||||
if (content && link) {
|
||||
content = (
|
||||
<Anchor href={link} style={{ textDecoration: 'none' }}>
|
||||
{content}
|
||||
</Anchor>
|
||||
);
|
||||
}
|
||||
|
||||
// Return content if it exists, else default
|
||||
if (content !== undefined) {
|
||||
return content;
|
||||
}
|
||||
return (
|
||||
<ThumbnailHoverCard
|
||||
src={data.thumbnail || data.image}
|
||||
text={data.name}
|
||||
link={link}
|
||||
/>
|
||||
);
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
import { ApiPaths } from '../../states/ApiState';
|
||||
import { GeneralRenderer } from './GeneralRenderer';
|
||||
|
||||
export const PartRenderer = ({
|
||||
pk,
|
||||
data = undefined,
|
||||
link = true
|
||||
}: {
|
||||
pk: string;
|
||||
data?: any;
|
||||
link?: boolean;
|
||||
}) => {
|
||||
return (
|
||||
<GeneralRenderer
|
||||
api_key={ApiPaths.part_list}
|
||||
api_ref="part"
|
||||
link={link ? `/part/${pk}` : ''}
|
||||
pk={pk}
|
||||
data={data}
|
||||
/>
|
||||
);
|
||||
};
|
@ -1,26 +0,0 @@
|
||||
import { Group } from '@mantine/core';
|
||||
|
||||
import { ApiPaths } from '../../states/ApiState';
|
||||
import { GeneralRenderer } from './GeneralRenderer';
|
||||
|
||||
export const PurchaseOrderRenderer = ({ pk }: { pk: string }) => {
|
||||
const DetailRenderer = (data: any) => {
|
||||
const code = data?.project_code_detail?.code;
|
||||
return (
|
||||
<Group position="apart">
|
||||
<div>{data?.reference}</div>
|
||||
{code && <div>({code})</div>}
|
||||
{data?.supplier_reference && <div>{data?.supplier_reference}</div>}
|
||||
</Group>
|
||||
);
|
||||
};
|
||||
return (
|
||||
<GeneralRenderer
|
||||
api_key={ApiPaths.purchase_order_list}
|
||||
api_ref="purchaseorder"
|
||||
link={`/order/purchase-order/${pk}`}
|
||||
pk={pk}
|
||||
renderer={DetailRenderer}
|
||||
/>
|
||||
);
|
||||
};
|
@ -1,16 +0,0 @@
|
||||
import { ApiPaths } from '../../states/ApiState';
|
||||
import { GeneralRenderer } from './GeneralRenderer';
|
||||
|
||||
export const SalesOrderRenderer = ({ pk }: { pk: string }) => {
|
||||
return (
|
||||
<GeneralRenderer
|
||||
api_key={ApiPaths.sales_order_list}
|
||||
api_ref="sales_order"
|
||||
link={`/order/so/${pk}`}
|
||||
pk={pk}
|
||||
renderer={(data: any) => {
|
||||
return data.reference;
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
@ -1,27 +0,0 @@
|
||||
import { Group } from '@mantine/core';
|
||||
|
||||
import { ApiPaths } from '../../states/ApiState';
|
||||
import { GeneralRenderer } from './GeneralRenderer';
|
||||
import { PartRenderer } from './PartRenderer';
|
||||
|
||||
export const StockItemRenderer = ({ pk }: { pk: string }) => {
|
||||
const DetailRenderer = (data: any) => {
|
||||
return (
|
||||
<Group position="apart">
|
||||
{data?.quantity}
|
||||
<small>
|
||||
<PartRenderer pk={data?.part_detail.pk} data={data?.part_detail} />
|
||||
</small>
|
||||
</Group>
|
||||
);
|
||||
};
|
||||
return (
|
||||
<GeneralRenderer
|
||||
api_key={ApiPaths.stock_item_list}
|
||||
api_ref="stockitem"
|
||||
link={`/stock/item/${pk}`}
|
||||
pk={pk}
|
||||
renderer={DetailRenderer}
|
||||
/>
|
||||
);
|
||||
};
|
@ -1,14 +0,0 @@
|
||||
import { ApiPaths } from '../../states/ApiState';
|
||||
import { GeneralRenderer } from './GeneralRenderer';
|
||||
|
||||
export const StockLocationRenderer = ({ pk }: { pk: string }) => {
|
||||
return (
|
||||
<GeneralRenderer
|
||||
api_key={ApiPaths.stock_location_list}
|
||||
api_ref="stock_location"
|
||||
link={`/stock/location/${pk}`}
|
||||
pk={pk}
|
||||
image={false}
|
||||
/>
|
||||
);
|
||||
};
|
@ -1,33 +0,0 @@
|
||||
import { Group } from '@mantine/core';
|
||||
|
||||
import { ApiPaths } from '../../states/ApiState';
|
||||
import { GeneralRenderer } from './GeneralRenderer';
|
||||
import { PartRenderer } from './PartRenderer';
|
||||
|
||||
export const SupplierPartRenderer = ({ pk }: { pk: string }) => {
|
||||
const DetailRenderer = (data: any) => {
|
||||
return (
|
||||
<Group position="apart">
|
||||
{data?.SKU}
|
||||
<small>
|
||||
<span style={{ color: 'white' }}>
|
||||
<PartRenderer
|
||||
pk={data?.part_detail?.pk}
|
||||
data={data?.part_detail}
|
||||
link={false}
|
||||
/>
|
||||
</span>
|
||||
</small>
|
||||
</Group>
|
||||
);
|
||||
};
|
||||
return (
|
||||
<GeneralRenderer
|
||||
api_key={ApiPaths.supplier_part_list}
|
||||
api_ref="supplier_part"
|
||||
link={`/supplier-part/${pk}`}
|
||||
pk={pk}
|
||||
renderer={DetailRenderer}
|
||||
/>
|
||||
);
|
||||
};
|
@ -1,39 +0,0 @@
|
||||
import { BuildOrderRenderer } from './BuildOrderRenderer';
|
||||
import { PartRenderer } from './PartRenderer';
|
||||
import { PurchaseOrderRenderer } from './PurchaseOrderRenderer';
|
||||
import { SalesOrderRenderer } from './SalesOrderRenderer';
|
||||
import { StockItemRenderer } from './StockItemRenderer';
|
||||
import { StockLocationRenderer } from './StockLocationRenderer';
|
||||
import { SupplierPartRenderer } from './SupplierPartRenderer';
|
||||
|
||||
export enum RenderTypes {
|
||||
part = 'part',
|
||||
stock_item = 'stockitem',
|
||||
stock_location = 'stocklocation',
|
||||
supplier_part = 'supplierpart',
|
||||
purchase_order = 'purchase_order',
|
||||
sales_order = 'sales_order',
|
||||
build_order = 'build_order'
|
||||
}
|
||||
|
||||
// dict of renderers
|
||||
const renderers = {
|
||||
[RenderTypes.part]: PartRenderer,
|
||||
[RenderTypes.stock_item]: StockItemRenderer,
|
||||
[RenderTypes.stock_location]: StockLocationRenderer,
|
||||
[RenderTypes.supplier_part]: SupplierPartRenderer,
|
||||
[RenderTypes.purchase_order]: PurchaseOrderRenderer,
|
||||
[RenderTypes.sales_order]: SalesOrderRenderer,
|
||||
[RenderTypes.build_order]: BuildOrderRenderer
|
||||
};
|
||||
|
||||
export interface RenderProps {
|
||||
type: RenderTypes;
|
||||
pk: string;
|
||||
}
|
||||
|
||||
export function Render(props: RenderProps) {
|
||||
const { type, ...rest } = props;
|
||||
const RendererComponent = renderers[type];
|
||||
return <RendererComponent {...rest} />;
|
||||
}
|
@ -4,11 +4,11 @@
|
||||
import { t } from '@lingui/macro';
|
||||
|
||||
import { formatCurrency, renderDate } from '../../defaults/formatters';
|
||||
import { ModelType } from '../../enums/ModelType';
|
||||
import { ProgressBar } from '../items/ProgressBar';
|
||||
import { YesNoButton } from '../items/YesNoButton';
|
||||
import { ModelType } from '../render/ModelType';
|
||||
import { TableStatusRenderer } from '../render/StatusRenderer';
|
||||
import { RenderOwner } from '../render/User';
|
||||
import { TableStatusRenderer } from '../renderers/StatusRenderer';
|
||||
import { TableColumn } from './Column';
|
||||
import { ProjectCodeHoverCard } from './TableHoverCard';
|
||||
|
||||
|
@ -56,7 +56,11 @@ export function ProjectCodeHoverCard({ projectCode }: { projectCode: any }) {
|
||||
<TableHoverCard
|
||||
value={projectCode?.code}
|
||||
title={t`Project Code`}
|
||||
extra={projectCode?.description}
|
||||
extra={
|
||||
projectCode && (
|
||||
<Text key="project-code">{projectCode?.description}</Text>
|
||||
)
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
'-'
|
||||
|
@ -8,11 +8,13 @@ import {
|
||||
import { ReactNode, useCallback, useMemo } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { ApiPaths } from '../../../enums/ApiEndpoints';
|
||||
import { UserRoles } from '../../../enums/Roles';
|
||||
import { bomItemFields } from '../../../forms/BomForms';
|
||||
import { openDeleteApiForm, openEditApiForm } from '../../../functions/forms';
|
||||
import { useTableRefresh } from '../../../hooks/TableRefresh';
|
||||
import { ApiPaths, apiUrl } from '../../../states/ApiState';
|
||||
import { UserRoles, useUserState } from '../../../states/UserState';
|
||||
import { apiUrl } from '../../../states/ApiState';
|
||||
import { useUserState } from '../../../states/UserState';
|
||||
import { Thumbnail } from '../../images/Thumbnail';
|
||||
import { YesNoButton } from '../../items/YesNoButton';
|
||||
import { TableColumn } from '../Column';
|
||||
@ -64,7 +66,9 @@ export function BomTable({
|
||||
let extra = [];
|
||||
|
||||
if (record.part != partId) {
|
||||
extra.push(t`This BOM item is defined for a different parent`);
|
||||
extra.push(
|
||||
<Text key="different-parent">{t`This BOM item is defined for a different parent`}</Text>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -2,8 +2,9 @@ import { t } from '@lingui/macro';
|
||||
import { useMemo } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { ApiPaths } from '../../../enums/ApiEndpoints';
|
||||
import { useTableRefresh } from '../../../hooks/TableRefresh';
|
||||
import { ApiPaths, apiUrl } from '../../../states/ApiState';
|
||||
import { apiUrl } from '../../../states/ApiState';
|
||||
import { ThumbnailHoverCard } from '../../images/Thumbnail';
|
||||
import { TableColumn } from '../Column';
|
||||
import { TableFilter } from '../Filter';
|
||||
|
@ -3,11 +3,12 @@ import { useMemo } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { renderDate } from '../../../defaults/formatters';
|
||||
import { ApiPaths } from '../../../enums/ApiEndpoints';
|
||||
import { ModelType } from '../../../enums/ModelType';
|
||||
import { useTableRefresh } from '../../../hooks/TableRefresh';
|
||||
import { ApiPaths, apiUrl } from '../../../states/ApiState';
|
||||
import { apiUrl } from '../../../states/ApiState';
|
||||
import { ThumbnailHoverCard } from '../../images/Thumbnail';
|
||||
import { ProgressBar } from '../../items/ProgressBar';
|
||||
import { ModelType } from '../../render/ModelType';
|
||||
import { RenderUser } from '../../render/User';
|
||||
import { TableColumn } from '../Column';
|
||||
import {
|
||||
|
@ -7,13 +7,14 @@ import { IconExternalLink, IconFileUpload } from '@tabler/icons-react';
|
||||
import { ReactNode, useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { api } from '../../../App';
|
||||
import { ApiPaths } from '../../../enums/ApiEndpoints';
|
||||
import {
|
||||
addAttachment,
|
||||
deleteAttachment,
|
||||
editAttachment
|
||||
} from '../../../forms/AttachmentForms';
|
||||
import { useTableRefresh } from '../../../hooks/TableRefresh';
|
||||
import { ApiPaths, apiUrl } from '../../../states/ApiState';
|
||||
import { apiUrl } from '../../../states/ApiState';
|
||||
import { AttachmentLink } from '../../items/AttachmentLink';
|
||||
import { TableColumn } from '../Column';
|
||||
import { InvenTreeTable } from '../InvenTreeTable';
|
||||
|
@ -3,8 +3,9 @@ import { Group, Text } from '@mantine/core';
|
||||
import { useMemo } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { ApiPaths } from '../../../enums/ApiEndpoints';
|
||||
import { useTableRefresh } from '../../../hooks/TableRefresh';
|
||||
import { ApiPaths, apiUrl } from '../../../states/ApiState';
|
||||
import { apiUrl } from '../../../states/ApiState';
|
||||
import { Thumbnail } from '../../images/Thumbnail';
|
||||
import { DescriptionColumn } from '../ColumnRenderers';
|
||||
import { InvenTreeTable } from '../InvenTreeTable';
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { t } from '@lingui/macro';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { ApiPaths, apiUrl } from '../../../states/ApiState';
|
||||
import { ApiPaths } from '../../../enums/ApiEndpoints';
|
||||
import { apiUrl } from '../../../states/ApiState';
|
||||
import { TableColumn } from '../Column';
|
||||
import { InvenTreeTable } from '../InvenTreeTable';
|
||||
import { RowAction } from '../RowActions';
|
||||
|
@ -2,8 +2,9 @@ import { t } from '@lingui/macro';
|
||||
import { useMemo } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { ApiPaths } from '../../../enums/ApiEndpoints';
|
||||
import { useTableRefresh } from '../../../hooks/TableRefresh';
|
||||
import { ApiPaths, apiUrl } from '../../../states/ApiState';
|
||||
import { apiUrl } from '../../../states/ApiState';
|
||||
import { TableColumn } from '../Column';
|
||||
import { DescriptionColumn } from '../ColumnRenderers';
|
||||
import { InvenTreeTable } from '../InvenTreeTable';
|
||||
|
@ -2,14 +2,16 @@ import { t } from '@lingui/macro';
|
||||
import { Group, Text } from '@mantine/core';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
|
||||
import { ApiPaths } from '../../../enums/ApiEndpoints';
|
||||
import { UserRoles } from '../../../enums/Roles';
|
||||
import {
|
||||
openCreateApiForm,
|
||||
openDeleteApiForm,
|
||||
openEditApiForm
|
||||
} from '../../../functions/forms';
|
||||
import { useTableRefresh } from '../../../hooks/TableRefresh';
|
||||
import { ApiPaths, apiUrl } from '../../../states/ApiState';
|
||||
import { UserRoles, useUserState } from '../../../states/UserState';
|
||||
import { apiUrl } from '../../../states/ApiState';
|
||||
import { useUserState } from '../../../states/UserState';
|
||||
import { AddItemButton } from '../../buttons/AddItemButton';
|
||||
import { Thumbnail } from '../../images/Thumbnail';
|
||||
import { YesNoButton } from '../../items/YesNoButton';
|
||||
@ -178,7 +180,7 @@ export function PartParameterTable({ partId }: { partId: any }) {
|
||||
|
||||
// TODO: Hide if user does not have permission to edit parts
|
||||
actions.push(
|
||||
<AddItemButton tooltip="Add parameter" onClick={addParameter} />
|
||||
<AddItemButton tooltip={t`Add parameter`} onClick={addParameter} />
|
||||
);
|
||||
|
||||
return actions;
|
||||
|
@ -2,6 +2,8 @@ import { t } from '@lingui/macro';
|
||||
import { Text } from '@mantine/core';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
|
||||
import { ApiPaths } from '../../../enums/ApiEndpoints';
|
||||
import { UserRoles } from '../../../enums/Roles';
|
||||
import { partParameterTemplateFields } from '../../../forms/PartForms';
|
||||
import {
|
||||
openCreateApiForm,
|
||||
@ -9,8 +11,8 @@ import {
|
||||
openEditApiForm
|
||||
} from '../../../functions/forms';
|
||||
import { useTableRefresh } from '../../../hooks/TableRefresh';
|
||||
import { ApiPaths, apiUrl } from '../../../states/ApiState';
|
||||
import { UserRoles, useUserState } from '../../../states/UserState';
|
||||
import { apiUrl } from '../../../states/ApiState';
|
||||
import { useUserState } from '../../../states/UserState';
|
||||
import { AddItemButton } from '../../buttons/AddItemButton';
|
||||
import { TableColumn } from '../Column';
|
||||
import { InvenTreeTable } from '../InvenTreeTable';
|
||||
|
@ -3,9 +3,10 @@ import { Group, Text } from '@mantine/core';
|
||||
import { ReactNode, useMemo } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { ApiPaths } from '../../../enums/ApiEndpoints';
|
||||
import { shortenString } from '../../../functions/tables';
|
||||
import { useTableRefresh } from '../../../hooks/TableRefresh';
|
||||
import { ApiPaths, apiUrl } from '../../../states/ApiState';
|
||||
import { apiUrl } from '../../../states/ApiState';
|
||||
import { Thumbnail } from '../../images/Thumbnail';
|
||||
import { TableColumn } from '../Column';
|
||||
import { DescriptionColumn, LinkColumn } from '../ColumnRenderers';
|
||||
@ -77,23 +78,29 @@ function partTableColumns(): TableColumn[] {
|
||||
|
||||
if (min_stock > stock) {
|
||||
extra.push(
|
||||
<Text color="orange">{t`Minimum stock` + `: ${min_stock}`}</Text>
|
||||
<Text key="min-stock" color="orange">
|
||||
{t`Minimum stock` + `: ${min_stock}`}
|
||||
</Text>
|
||||
);
|
||||
|
||||
color = 'orange';
|
||||
}
|
||||
|
||||
if (record.ordering > 0) {
|
||||
extra.push(<Text>{t`On Order` + `: ${record.ordering}`}</Text>);
|
||||
extra.push(
|
||||
<Text key="on-order">{t`On Order` + `: ${record.ordering}`}</Text>
|
||||
);
|
||||
}
|
||||
|
||||
if (record.building) {
|
||||
extra.push(<Text>{t`Building` + `: ${record.building}`}</Text>);
|
||||
extra.push(
|
||||
<Text key="building">{t`Building` + `: ${record.building}`}</Text>
|
||||
);
|
||||
}
|
||||
|
||||
if (record.allocated_to_build_orders > 0) {
|
||||
extra.push(
|
||||
<Text>
|
||||
<Text key="bo-allocations">
|
||||
{t`Build Order Allocations` +
|
||||
`: ${record.allocated_to_build_orders}`}
|
||||
</Text>
|
||||
@ -102,7 +109,7 @@ function partTableColumns(): TableColumn[] {
|
||||
|
||||
if (record.allocated_to_sales_orders > 0) {
|
||||
extra.push(
|
||||
<Text>
|
||||
<Text key="so-allocations">
|
||||
{t`Sales Order Allocations` +
|
||||
`: ${record.allocated_to_sales_orders}`}
|
||||
</Text>
|
||||
@ -110,7 +117,9 @@ function partTableColumns(): TableColumn[] {
|
||||
}
|
||||
|
||||
if (available != stock) {
|
||||
extra.push(<Text>{t`Available` + `: ${available}`}</Text>);
|
||||
extra.push(
|
||||
<Text key="available">{t`Available` + `: ${available}`}</Text>
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: Add extra information on stock "demand"
|
||||
|
@ -4,10 +4,12 @@ import { IconLayersLinked } from '@tabler/icons-react';
|
||||
import { ReactNode, useCallback, useMemo } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { ApiPaths } from '../../../enums/ApiEndpoints';
|
||||
import { UserRoles } from '../../../enums/Roles';
|
||||
import { openCreateApiForm, openDeleteApiForm } from '../../../functions/forms';
|
||||
import { useTableRefresh } from '../../../hooks/TableRefresh';
|
||||
import { ApiPaths, apiUrl } from '../../../states/ApiState';
|
||||
import { UserRoles, useUserState } from '../../../states/UserState';
|
||||
import { apiUrl } from '../../../states/ApiState';
|
||||
import { useUserState } from '../../../states/UserState';
|
||||
import { Thumbnail } from '../../images/Thumbnail';
|
||||
import { TableColumn } from '../Column';
|
||||
import { InvenTreeTable } from '../InvenTreeTable';
|
||||
|
@ -10,8 +10,9 @@ import {
|
||||
import { useCallback, useMemo } from 'react';
|
||||
|
||||
import { api } from '../../../App';
|
||||
import { ApiPaths } from '../../../enums/ApiEndpoints';
|
||||
import { useTableRefresh } from '../../../hooks/TableRefresh';
|
||||
import { ApiPaths, apiUrl } from '../../../states/ApiState';
|
||||
import { apiUrl } from '../../../states/ApiState';
|
||||
import { StylishText } from '../../items/StylishText';
|
||||
import { TableColumn } from '../Column';
|
||||
import { InvenTreeTable, InvenTreeTableProps } from '../InvenTreeTable';
|
||||
|
@ -4,11 +4,13 @@ import { IconSquareArrowRight } from '@tabler/icons-react';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
|
||||
import { ProgressBar } from '../../../components/items/ProgressBar';
|
||||
import { ApiPaths } from '../../../enums/ApiEndpoints';
|
||||
import { UserRoles } from '../../../enums/Roles';
|
||||
import { purchaseOrderLineItemFields } from '../../../forms/PurchaseOrderForms';
|
||||
import { openCreateApiForm, openEditApiForm } from '../../../functions/forms';
|
||||
import { useTableRefresh } from '../../../hooks/TableRefresh';
|
||||
import { ApiPaths, apiUrl } from '../../../states/ApiState';
|
||||
import { UserRoles, useUserState } from '../../../states/UserState';
|
||||
import { apiUrl } from '../../../states/ApiState';
|
||||
import { useUserState } from '../../../states/UserState';
|
||||
import { ActionButton } from '../../buttons/ActionButton';
|
||||
import { AddItemButton } from '../../buttons/AddItemButton';
|
||||
import { Thumbnail } from '../../images/Thumbnail';
|
||||
@ -64,7 +66,8 @@ export function PurchaseOrderLineItemTable({
|
||||
}
|
||||
|
||||
let fields = purchaseOrderLineItemFields({
|
||||
supplierId: supplier
|
||||
supplierId: supplier,
|
||||
create: false
|
||||
});
|
||||
|
||||
openEditApiForm({
|
||||
@ -219,21 +222,29 @@ export function PurchaseOrderLineItemTable({
|
||||
openCreateApiForm({
|
||||
url: ApiPaths.purchase_order_line_list,
|
||||
title: t`Add Line Item`,
|
||||
fields: purchaseOrderLineItemFields({}),
|
||||
fields: purchaseOrderLineItemFields({
|
||||
create: true,
|
||||
orderId: orderId
|
||||
}),
|
||||
onFormSuccess: refreshTable,
|
||||
successMessage: t`Line item added`
|
||||
});
|
||||
}, []);
|
||||
}, [orderId]);
|
||||
|
||||
// Custom table actions
|
||||
const tableActions = useMemo(() => {
|
||||
return [
|
||||
<AddItemButton
|
||||
key="add-line-item"
|
||||
tooltip={t`Add line item`}
|
||||
onClick={addLine}
|
||||
hidden={!user?.hasAddRole(UserRoles.purchase_order)}
|
||||
/>,
|
||||
<ActionButton text={t`Receive items`} icon={<IconSquareArrowRight />} />
|
||||
<ActionButton
|
||||
key="receive-items"
|
||||
text={t`Receive items`}
|
||||
icon={<IconSquareArrowRight />}
|
||||
/>
|
||||
];
|
||||
}, [orderId, user]);
|
||||
|
||||
|
@ -2,10 +2,11 @@ import { t } from '@lingui/macro';
|
||||
import { useMemo } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { ApiPaths } from '../../../enums/ApiEndpoints';
|
||||
import { ModelType } from '../../../enums/ModelType';
|
||||
import { useTableRefresh } from '../../../hooks/TableRefresh';
|
||||
import { ApiPaths, apiUrl } from '../../../states/ApiState';
|
||||
import { apiUrl } from '../../../states/ApiState';
|
||||
import { Thumbnail } from '../../images/Thumbnail';
|
||||
import { ModelType } from '../../render/ModelType';
|
||||
import {
|
||||
CreationDateColumn,
|
||||
DescriptionColumn,
|
||||
|
@ -2,6 +2,8 @@ import { t } from '@lingui/macro';
|
||||
import { Text } from '@mantine/core';
|
||||
import { ReactNode, useCallback, useMemo } from 'react';
|
||||
|
||||
import { ApiPaths } from '../../../enums/ApiEndpoints';
|
||||
import { UserRoles } from '../../../enums/Roles';
|
||||
import { supplierPartFields } from '../../../forms/CompanyForms';
|
||||
import {
|
||||
openCreateApiForm,
|
||||
@ -9,8 +11,8 @@ import {
|
||||
openEditApiForm
|
||||
} from '../../../functions/forms';
|
||||
import { useTableRefresh } from '../../../hooks/TableRefresh';
|
||||
import { ApiPaths, apiUrl } from '../../../states/ApiState';
|
||||
import { UserRoles, useUserState } from '../../../states/UserState';
|
||||
import { apiUrl } from '../../../states/ApiState';
|
||||
import { useUserState } from '../../../states/UserState';
|
||||
import { AddItemButton } from '../../buttons/AddItemButton';
|
||||
import { Thumbnail } from '../../images/Thumbnail';
|
||||
import { TableColumn } from '../Column';
|
||||
@ -110,7 +112,7 @@ export function SupplierPartTable({ params }: { params: any }): ReactNode {
|
||||
|
||||
if (part.units) {
|
||||
extra.push(
|
||||
<Text>
|
||||
<Text key="base">
|
||||
{t`Base units`} : {part.units}
|
||||
</Text>
|
||||
);
|
||||
|
@ -2,10 +2,11 @@ import { t } from '@lingui/macro';
|
||||
import { useMemo } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { ApiPaths } from '../../../enums/ApiEndpoints';
|
||||
import { ModelType } from '../../../enums/ModelType';
|
||||
import { useTableRefresh } from '../../../hooks/TableRefresh';
|
||||
import { ApiPaths, apiUrl } from '../../../states/ApiState';
|
||||
import { apiUrl } from '../../../states/ApiState';
|
||||
import { Thumbnail } from '../../images/Thumbnail';
|
||||
import { ModelType } from '../../render/ModelType';
|
||||
import {
|
||||
CreationDateColumn,
|
||||
DescriptionColumn,
|
||||
|
@ -2,10 +2,11 @@ import { t } from '@lingui/macro';
|
||||
import { useMemo } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { ApiPaths } from '../../../enums/ApiEndpoints';
|
||||
import { ModelType } from '../../../enums/ModelType';
|
||||
import { useTableRefresh } from '../../../hooks/TableRefresh';
|
||||
import { ApiPaths, apiUrl } from '../../../states/ApiState';
|
||||
import { apiUrl } from '../../../states/ApiState';
|
||||
import { Thumbnail } from '../../images/Thumbnail';
|
||||
import { ModelType } from '../../render/ModelType';
|
||||
import {
|
||||
CreationDateColumn,
|
||||
DescriptionColumn,
|
||||
|
@ -4,8 +4,9 @@ import { IconReload } from '@tabler/icons-react';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
|
||||
import { api } from '../../../App';
|
||||
import { ApiPaths } from '../../../enums/ApiEndpoints';
|
||||
import { useTableRefresh } from '../../../hooks/TableRefresh';
|
||||
import { ApiPaths, apiUrl } from '../../../states/ApiState';
|
||||
import { apiUrl } from '../../../states/ApiState';
|
||||
import { ActionButton } from '../../buttons/ActionButton';
|
||||
import { InvenTreeTable } from '../InvenTreeTable';
|
||||
|
||||
|
@ -2,14 +2,16 @@ import { t } from '@lingui/macro';
|
||||
import { Text } from '@mantine/core';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
|
||||
import { ApiPaths } from '../../../enums/ApiEndpoints';
|
||||
import { UserRoles } from '../../../enums/Roles';
|
||||
import {
|
||||
openCreateApiForm,
|
||||
openDeleteApiForm,
|
||||
openEditApiForm
|
||||
} from '../../../functions/forms';
|
||||
import { useTableRefresh } from '../../../hooks/TableRefresh';
|
||||
import { ApiPaths, apiUrl } from '../../../states/ApiState';
|
||||
import { UserRoles, useUserState } from '../../../states/UserState';
|
||||
import { apiUrl } from '../../../states/ApiState';
|
||||
import { useUserState } from '../../../states/UserState';
|
||||
import { AddItemButton } from '../../buttons/AddItemButton';
|
||||
import { TableColumn } from '../Column';
|
||||
import { InvenTreeTable } from '../InvenTreeTable';
|
||||
|
@ -2,14 +2,16 @@ import { t } from '@lingui/macro';
|
||||
import { Text } from '@mantine/core';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
|
||||
import { ApiPaths } from '../../../enums/ApiEndpoints';
|
||||
import { UserRoles } from '../../../enums/Roles';
|
||||
import {
|
||||
openCreateApiForm,
|
||||
openDeleteApiForm,
|
||||
openEditApiForm
|
||||
} from '../../../functions/forms';
|
||||
import { useTableRefresh } from '../../../hooks/TableRefresh';
|
||||
import { ApiPaths, apiUrl } from '../../../states/ApiState';
|
||||
import { UserRoles, useUserState } from '../../../states/UserState';
|
||||
import { apiUrl } from '../../../states/ApiState';
|
||||
import { useUserState } from '../../../states/UserState';
|
||||
import { AddItemButton } from '../../buttons/AddItemButton';
|
||||
import { TableColumn } from '../Column';
|
||||
import { DescriptionColumn } from '../ColumnRenderers';
|
||||
|
@ -4,10 +4,11 @@ import { ReactNode, useMemo } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { formatCurrency, renderDate } from '../../../defaults/formatters';
|
||||
import { ApiPaths } from '../../../enums/ApiEndpoints';
|
||||
import { ModelType } from '../../../enums/ModelType';
|
||||
import { useTableRefresh } from '../../../hooks/TableRefresh';
|
||||
import { ApiPaths, apiUrl } from '../../../states/ApiState';
|
||||
import { apiUrl } from '../../../states/ApiState';
|
||||
import { Thumbnail } from '../../images/Thumbnail';
|
||||
import { ModelType } from '../../render/ModelType';
|
||||
import { TableColumn } from '../Column';
|
||||
import { StatusColumn } from '../ColumnRenderers';
|
||||
import { TableFilter } from '../Filter';
|
||||
@ -65,49 +66,77 @@ function stockItemTableColumns(): TableColumn[] {
|
||||
if (record.is_building) {
|
||||
color = 'blue';
|
||||
extra.push(
|
||||
<Text size="sm">{t`This stock item is in production`}</Text>
|
||||
<Text
|
||||
key="production"
|
||||
size="sm"
|
||||
>{t`This stock item is in production`}</Text>
|
||||
);
|
||||
}
|
||||
|
||||
if (record.sales_order) {
|
||||
extra.push(
|
||||
<Text size="sm">{t`This stock item has been assigned to a sales order`}</Text>
|
||||
<Text
|
||||
key="sales-order"
|
||||
size="sm"
|
||||
>{t`This stock item has been assigned to a sales order`}</Text>
|
||||
);
|
||||
}
|
||||
|
||||
if (record.customer) {
|
||||
extra.push(
|
||||
<Text size="sm">{t`This stock item has been assigned to a customer`}</Text>
|
||||
<Text
|
||||
key="customer"
|
||||
size="sm"
|
||||
>{t`This stock item has been assigned to a customer`}</Text>
|
||||
);
|
||||
}
|
||||
|
||||
if (record.belongs_to) {
|
||||
extra.push(
|
||||
<Text size="sm">{t`This stock item is installed in another stock item`}</Text>
|
||||
<Text
|
||||
key="belongs-to"
|
||||
size="sm"
|
||||
>{t`This stock item is installed in another stock item`}</Text>
|
||||
);
|
||||
}
|
||||
|
||||
if (record.consumed_by) {
|
||||
extra.push(
|
||||
<Text size="sm">{t`This stock item has been consumed by a build order`}</Text>
|
||||
<Text
|
||||
key="consumed-by"
|
||||
size="sm"
|
||||
>{t`This stock item has been consumed by a build order`}</Text>
|
||||
);
|
||||
}
|
||||
|
||||
if (record.expired) {
|
||||
extra.push(<Text size="sm">{t`This stock item has expired`}</Text>);
|
||||
extra.push(
|
||||
<Text
|
||||
key="expired"
|
||||
size="sm"
|
||||
>{t`This stock item has expired`}</Text>
|
||||
);
|
||||
} else if (record.stale) {
|
||||
extra.push(<Text size="sm">{t`This stock item is stale`}</Text>);
|
||||
extra.push(
|
||||
<Text key="stale" size="sm">{t`This stock item is stale`}</Text>
|
||||
);
|
||||
}
|
||||
|
||||
if (allocated > 0) {
|
||||
if (allocated >= quantity) {
|
||||
color = 'orange';
|
||||
extra.push(
|
||||
<Text size="sm">{t`This stock item is fully allocated`}</Text>
|
||||
<Text
|
||||
key="fully-allocated"
|
||||
size="sm"
|
||||
>{t`This stock item is fully allocated`}</Text>
|
||||
);
|
||||
} else {
|
||||
extra.push(
|
||||
<Text size="sm">{t`This stock item is partially allocated`}</Text>
|
||||
<Text
|
||||
key="partially-allocated"
|
||||
size="sm"
|
||||
>{t`This stock item is partially allocated`}</Text>
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -115,13 +144,17 @@ function stockItemTableColumns(): TableColumn[] {
|
||||
if (available != quantity) {
|
||||
if (available > 0) {
|
||||
extra.push(
|
||||
<Text size="sm" color="orange">
|
||||
<Text key="available" size="sm" color="orange">
|
||||
{t`Available` + `: ${available}`}
|
||||
</Text>
|
||||
);
|
||||
} else {
|
||||
extra.push(
|
||||
<Text size="sm" color="red">{t`No stock available`}</Text>
|
||||
<Text
|
||||
key="no-stock"
|
||||
size="sm"
|
||||
color="red"
|
||||
>{t`No stock available`}</Text>
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -129,7 +162,10 @@ function stockItemTableColumns(): TableColumn[] {
|
||||
if (quantity <= 0) {
|
||||
color = 'red';
|
||||
extra.push(
|
||||
<Text size="sm">{t`This stock item has been depleted`}</Text>
|
||||
<Text
|
||||
key="depleted"
|
||||
size="sm"
|
||||
>{t`This stock item has been depleted`}</Text>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -2,8 +2,9 @@ import { t } from '@lingui/macro';
|
||||
import { useMemo } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { ApiPaths } from '../../../enums/ApiEndpoints';
|
||||
import { useTableRefresh } from '../../../hooks/TableRefresh';
|
||||
import { ApiPaths, apiUrl } from '../../../states/ApiState';
|
||||
import { apiUrl } from '../../../states/ApiState';
|
||||
import { YesNoButton } from '../../items/YesNoButton';
|
||||
import { TableColumn } from '../Column';
|
||||
import { DescriptionColumn } from '../ColumnRenderers';
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { ModelType } from '../components/render/ModelType';
|
||||
import { ModelType } from '../enums/ModelType';
|
||||
|
||||
/* Lookup tables for mapping backend responses to internal types */
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { t } from '@lingui/macro';
|
||||
|
||||
import { ApiPaths } from '../states/ApiState';
|
||||
import { ApiPaths } from '../enums/ApiEndpoints';
|
||||
|
||||
interface DashboardItems {
|
||||
id: string;
|
||||
|
89
src/frontend/src/enums/ApiEndpoints.tsx
Normal file
89
src/frontend/src/enums/ApiEndpoints.tsx
Normal file
@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Enumeration of available API endpoints.
|
||||
*/
|
||||
export enum ApiPaths {
|
||||
api_server_info = 'api-server-info',
|
||||
|
||||
api_search = 'api-search',
|
||||
|
||||
// User information
|
||||
user_me = 'api-user-me',
|
||||
user_roles = 'api-user-roles',
|
||||
user_token = 'api-user-token',
|
||||
user_simple_login = 'api-user-simple-login',
|
||||
user_reset = 'api-user-reset',
|
||||
user_reset_set = 'api-user-reset-set',
|
||||
user_sso = 'api-user-sso',
|
||||
user_sso_remove = 'api-user-sso-remove',
|
||||
user_emails = 'api-user-emails',
|
||||
user_email_verify = 'api-user-email-verify',
|
||||
user_email_primary = 'api-user-email-primary',
|
||||
user_email_remove = 'api-user-email-remove',
|
||||
|
||||
user_list = 'api-user-list',
|
||||
owner_list = 'api-owner-list',
|
||||
|
||||
settings_global_list = 'api-settings-global-list',
|
||||
settings_user_list = 'api-settings-user-list',
|
||||
notifications_list = 'api-notifications-list',
|
||||
|
||||
currency_list = 'api-currency-list',
|
||||
currency_refresh = 'api-currency-refresh',
|
||||
|
||||
barcode = 'api-barcode',
|
||||
news = 'news',
|
||||
global_status = 'api-global-status',
|
||||
version = 'api-version',
|
||||
sso_providers = 'api-sso-providers',
|
||||
|
||||
// Build order URLs
|
||||
build_order_list = 'api-build-list',
|
||||
build_order_attachment_list = 'api-build-attachment-list',
|
||||
|
||||
// BOM URLs
|
||||
bom_list = 'api-bom-list',
|
||||
|
||||
// Part URLs
|
||||
part_list = 'api-part-list',
|
||||
category_list = 'api-category-list',
|
||||
category_tree = 'api-category-tree',
|
||||
related_part_list = 'api-related-part-list',
|
||||
part_attachment_list = 'api-part-attachment-list',
|
||||
part_parameter_list = 'api-part-parameter-list',
|
||||
part_parameter_template_list = 'api-part-parameter-template-list',
|
||||
|
||||
// Company URLs
|
||||
company_list = 'api-company-list',
|
||||
company_attachment_list = 'api-company-attachment-list',
|
||||
supplier_part_list = 'api-supplier-part-list',
|
||||
manufacturer_part_list = 'api-manufacturer-part-list',
|
||||
address_list = 'api-address-list',
|
||||
contact_list = 'api-contact-list',
|
||||
|
||||
// Stock Item URLs
|
||||
stock_item_list = 'api-stock-item-list',
|
||||
stock_tracking_list = 'api-stock-tracking-list',
|
||||
stock_location_list = 'api-stock-location-list',
|
||||
stock_location_tree = 'api-stock-location-tree',
|
||||
stock_attachment_list = 'api-stock-attachment-list',
|
||||
|
||||
// Purchase Order URLs
|
||||
purchase_order_list = 'api-purchase-order-list',
|
||||
purchase_order_line_list = 'api-purchase-order-line-list',
|
||||
purchase_order_attachment_list = 'api-purchase-order-attachment-list',
|
||||
|
||||
// Sales Order URLs
|
||||
sales_order_list = 'api-sales-order-list',
|
||||
sales_order_attachment_list = 'api-sales-order-attachment-list',
|
||||
sales_order_shipment_list = 'api_sales_order_shipment_list',
|
||||
|
||||
// Return Order URLs
|
||||
return_order_list = 'api-return-order-list',
|
||||
return_order_attachment_list = 'api-return-order-attachment-list',
|
||||
|
||||
// Plugin URLs
|
||||
plugin_list = 'api-plugin-list',
|
||||
|
||||
project_code_list = 'api-project-code-list',
|
||||
custom_unit_list = 'api-custom-unit-list'
|
||||
}
|
25
src/frontend/src/enums/ModelType.tsx
Normal file
25
src/frontend/src/enums/ModelType.tsx
Normal file
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Enumeration of available API model types
|
||||
*/
|
||||
export enum ModelType {
|
||||
part = 'part',
|
||||
supplierpart = 'supplierpart',
|
||||
manufacturerpart = 'manufacturerpart',
|
||||
partcategory = 'partcategory',
|
||||
partparametertemplate = 'partparametertemplate',
|
||||
projectcode = 'projectcode',
|
||||
stockitem = 'stockitem',
|
||||
stocklocation = 'stocklocation',
|
||||
stockhistory = 'stockhistory',
|
||||
build = 'build',
|
||||
company = 'company',
|
||||
purchaseorder = 'purchaseorder',
|
||||
purchaseorderline = 'purchaseorderline',
|
||||
salesorder = 'salesorder',
|
||||
salesordershipment = 'salesordershipment',
|
||||
returnorder = 'returnorder',
|
||||
address = 'address',
|
||||
contact = 'contact',
|
||||
owner = 'owner',
|
||||
user = 'user'
|
||||
}
|
25
src/frontend/src/enums/Roles.tsx
Normal file
25
src/frontend/src/enums/Roles.tsx
Normal file
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Enumeration of available user role groups
|
||||
*/
|
||||
export enum UserRoles {
|
||||
admin = 'admin',
|
||||
build = 'build',
|
||||
part = 'part',
|
||||
part_category = 'part_category',
|
||||
purchase_order = 'purchase_order',
|
||||
return_order = 'return_order',
|
||||
sales_order = 'sales_order',
|
||||
stock = 'stock',
|
||||
stock_location = 'stocklocation',
|
||||
stocktake = 'stocktake'
|
||||
}
|
||||
|
||||
/*
|
||||
* Enumeration of available user permissions within each role group
|
||||
*/
|
||||
export enum UserPermissions {
|
||||
view = 'view',
|
||||
add = 'add',
|
||||
change = 'change',
|
||||
delete = 'delete'
|
||||
}
|
@ -2,12 +2,12 @@ import { t } from '@lingui/macro';
|
||||
import { Text } from '@mantine/core';
|
||||
|
||||
import { ApiFormFieldSet } from '../components/forms/fields/ApiFormField';
|
||||
import { ApiPaths } from '../enums/ApiEndpoints';
|
||||
import {
|
||||
openCreateApiForm,
|
||||
openDeleteApiForm,
|
||||
openEditApiForm
|
||||
} from '../functions/forms';
|
||||
import { ApiPaths } from '../states/ApiState';
|
||||
|
||||
export function attachmentFields(editing: boolean): ApiFormFieldSet {
|
||||
let fields: ApiFormFieldSet = {
|
||||
|
@ -14,8 +14,8 @@ import {
|
||||
ApiFormData,
|
||||
ApiFormFieldSet
|
||||
} from '../components/forms/fields/ApiFormField';
|
||||
import { ApiPaths } from '../enums/ApiEndpoints';
|
||||
import { openEditApiForm } from '../functions/forms';
|
||||
import { ApiPaths } from '../states/ApiState';
|
||||
|
||||
/**
|
||||
* Field set for SupplierPart instance
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { t } from '@lingui/macro';
|
||||
|
||||
import { ApiFormFieldSet } from '../components/forms/fields/ApiFormField';
|
||||
import { ApiPaths } from '../enums/ApiEndpoints';
|
||||
import { openCreateApiForm, openEditApiForm } from '../functions/forms';
|
||||
import { ApiPaths } from '../states/ApiState';
|
||||
|
||||
/**
|
||||
* Construct a set of fields for creating / editing a Part instance
|
||||
|
@ -16,15 +16,21 @@ import {
|
||||
* Construct a set of fields for creating / editing a PurchaseOrderLineItem instance
|
||||
*/
|
||||
export function purchaseOrderLineItemFields({
|
||||
supplierId
|
||||
supplierId,
|
||||
orderId,
|
||||
create = false
|
||||
}: {
|
||||
supplierId?: number;
|
||||
orderId?: number;
|
||||
create?: boolean;
|
||||
}) {
|
||||
let fields: ApiFormFieldSet = {
|
||||
order: {
|
||||
filters: {
|
||||
supplier_detail: true
|
||||
}
|
||||
},
|
||||
value: orderId,
|
||||
hidden: create != true || orderId != undefined
|
||||
},
|
||||
part: {
|
||||
filters: {
|
||||
|
@ -5,8 +5,8 @@ import {
|
||||
ApiFormData,
|
||||
ApiFormFieldSet
|
||||
} from '../components/forms/fields/ApiFormField';
|
||||
import { ApiPaths } from '../enums/ApiEndpoints';
|
||||
import { openCreateApiForm, openEditApiForm } from '../functions/forms';
|
||||
import { ApiPaths } from '../states/ApiState';
|
||||
|
||||
/**
|
||||
* Construct a set of fields for creating / editing a StockItem instance
|
||||
|
@ -4,7 +4,8 @@ import { IconCheck } from '@tabler/icons-react';
|
||||
import axios from 'axios';
|
||||
|
||||
import { api } from '../App';
|
||||
import { ApiPaths, apiUrl, useServerApiState } from '../states/ApiState';
|
||||
import { ApiPaths } from '../enums/ApiEndpoints';
|
||||
import { apiUrl, useServerApiState } from '../states/ApiState';
|
||||
import { useLocalState } from '../states/LocalState';
|
||||
import { useSessionState } from '../states/SessionState';
|
||||
import {
|
||||
|
21
src/frontend/src/functions/conversion.tsx
Normal file
21
src/frontend/src/functions/conversion.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Determine if the provided value is "true":
|
||||
*
|
||||
* Many settings stored on the server are true/false,
|
||||
* but stored as string values, "true" / "false".
|
||||
*
|
||||
* This function provides a wrapper to ensure that the return type is boolean
|
||||
*/
|
||||
export function isTrue(value: any): boolean {
|
||||
if (value === true) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (value === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let s = String(value).trim().toLowerCase();
|
||||
|
||||
return ['true', 'yes', '1', 'on', 't', 'y'].includes(s);
|
||||
}
|
@ -2,7 +2,8 @@ import { useQuery } from '@tanstack/react-query';
|
||||
import { useCallback, useState } from 'react';
|
||||
|
||||
import { api } from '../App';
|
||||
import { ApiPaths, apiUrl } from '../states/ApiState';
|
||||
import { ApiPaths } from '../enums/ApiEndpoints';
|
||||
import { apiUrl } from '../states/ApiState';
|
||||
|
||||
/**
|
||||
* Custom hook for loading a single instance of an instance from the API
|
||||
|
@ -14,7 +14,8 @@ import { useNavigate, useSearchParams } from 'react-router-dom';
|
||||
|
||||
import { api } from '../../App';
|
||||
import { LanguageContext } from '../../contexts/LanguageContext';
|
||||
import { ApiPaths, apiUrl } from '../../states/ApiState';
|
||||
import { ApiPaths } from '../../enums/ApiEndpoints';
|
||||
import { apiUrl } from '../../states/ApiState';
|
||||
|
||||
export default function Set_Password() {
|
||||
const simpleForm = useForm({ initialValues: { password: '' } });
|
||||
|
@ -7,8 +7,9 @@ import { ReactNode, useState } from 'react';
|
||||
import { ApiFormProps } from '../../components/forms/ApiForm';
|
||||
import { PlaceholderPill } from '../../components/items/Placeholder';
|
||||
import { StylishText } from '../../components/items/StylishText';
|
||||
import { ModelType } from '../../components/render/ModelType';
|
||||
import { StatusRenderer } from '../../components/renderers/StatusRenderer';
|
||||
import { StatusRenderer } from '../../components/render/StatusRenderer';
|
||||
import { ApiPaths } from '../../enums/ApiEndpoints';
|
||||
import { ModelType } from '../../enums/ModelType';
|
||||
import {
|
||||
createPart,
|
||||
editPart,
|
||||
@ -16,7 +17,6 @@ import {
|
||||
} from '../../forms/PartForms';
|
||||
import { createStockItem } from '../../forms/StockForms';
|
||||
import { openCreateApiForm, openEditApiForm } from '../../functions/forms';
|
||||
import { ApiPaths } from '../../states/ApiState';
|
||||
|
||||
// Generate some example forms using the modal API forms interface
|
||||
function ApiFormsPlayground() {
|
||||
|
@ -47,37 +47,44 @@ import { api } from '../../App';
|
||||
import { DocInfo } from '../../components/items/DocInfo';
|
||||
import { StylishText } from '../../components/items/StylishText';
|
||||
import { TitleWithDoc } from '../../components/items/TitleWithDoc';
|
||||
import { Render, RenderTypes } from '../../components/renderers';
|
||||
import { RenderInstance } from '../../components/render/Instance';
|
||||
import { ModelInformationDict } from '../../components/render/ModelType';
|
||||
import { ApiPaths } from '../../enums/ApiEndpoints';
|
||||
import { ModelType } from '../../enums/ModelType';
|
||||
import { notYetImplemented } from '../../functions/notifications';
|
||||
import { IS_DEV_OR_DEMO } from '../../main';
|
||||
import { ApiPaths, apiUrl } from '../../states/ApiState';
|
||||
import { apiUrl } from '../../states/ApiState';
|
||||
|
||||
interface ScanItem {
|
||||
id: string;
|
||||
ref: string;
|
||||
data: any;
|
||||
instance?: any;
|
||||
timestamp: Date;
|
||||
source: string;
|
||||
link?: string;
|
||||
objectType?: RenderTypes;
|
||||
objectPk?: string;
|
||||
model?: ModelType;
|
||||
pk?: string;
|
||||
}
|
||||
|
||||
function matchObject(rd: any): [RenderTypes | undefined, string | undefined] {
|
||||
/*
|
||||
* Match the scanned object to a known internal model type
|
||||
*/
|
||||
function matchObject(rd: any): [ModelType | undefined, string | undefined] {
|
||||
if (rd?.part) {
|
||||
return [RenderTypes.part, rd?.part.pk];
|
||||
return [ModelType.part, rd?.part.pk];
|
||||
} else if (rd?.stockitem) {
|
||||
return [RenderTypes.stock_item, rd?.stockitem.pk];
|
||||
return [ModelType.stockitem, rd?.stockitem.pk];
|
||||
} else if (rd?.stocklocation) {
|
||||
return [RenderTypes.stock_location, rd?.stocklocation.pk];
|
||||
return [ModelType.stocklocation, rd?.stocklocation.pk];
|
||||
} else if (rd?.supplierpart) {
|
||||
return [RenderTypes.supplier_part, rd?.supplierpart.pk];
|
||||
return [ModelType.supplierpart, rd?.supplierpart.pk];
|
||||
} else if (rd?.purchaseorder) {
|
||||
return [RenderTypes.purchase_order, rd?.purchaseorder.pk];
|
||||
return [ModelType.purchaseorder, rd?.purchaseorder.pk];
|
||||
} else if (rd?.salesorder) {
|
||||
return [RenderTypes.sales_order, rd?.salesorder.pk];
|
||||
return [ModelType.salesorder, rd?.salesorder.pk];
|
||||
} else if (rd?.build) {
|
||||
return [RenderTypes.build_order, rd?.build.pk];
|
||||
return [ModelType.build, rd?.build.pk];
|
||||
} else {
|
||||
return [undefined, undefined];
|
||||
}
|
||||
@ -147,8 +154,27 @@ export default function Scan() {
|
||||
item.link = response.data?.url;
|
||||
|
||||
const rsp = matchObject(response.data);
|
||||
item.objectType = rsp[0];
|
||||
item.objectPk = rsp[1];
|
||||
item.model = rsp[0];
|
||||
item.pk = rsp[1];
|
||||
|
||||
// Fetch instance data
|
||||
if (item.model && item.pk) {
|
||||
let model_info = ModelInformationDict[item.model];
|
||||
|
||||
if (model_info && model_info.api_endpoint) {
|
||||
let url = apiUrl(model_info.api_endpoint, item.pk);
|
||||
|
||||
api
|
||||
.get(url)
|
||||
.then((response) => {
|
||||
item.instance = response.data;
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('error while fetching instance data at', url);
|
||||
console.info(err);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
historyHandlers.setState(history);
|
||||
})
|
||||
@ -206,7 +232,7 @@ export default function Scan() {
|
||||
...new Set(
|
||||
selection
|
||||
.map((id) => {
|
||||
return history.find((item) => item.id === id)?.objectType;
|
||||
return history.find((item) => item.id === id)?.model;
|
||||
})
|
||||
.filter((item) => item != undefined)
|
||||
)
|
||||
@ -384,13 +410,13 @@ function HistoryTable({
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
{item.objectPk && item.objectType ? (
|
||||
<Render type={item.objectType} pk={item.objectPk} />
|
||||
{item.pk && item.model && item.instance ? (
|
||||
<RenderInstance model={item.model} instance={item.instance} />
|
||||
) : (
|
||||
item.ref
|
||||
)}
|
||||
</td>
|
||||
<td>{item.objectType}</td>
|
||||
<td>{item.model}</td>
|
||||
<td>{item.source}</td>
|
||||
<td>{item.timestamp?.toString()}</td>
|
||||
</tr>
|
||||
|
@ -5,7 +5,8 @@ import { useToggle } from '@mantine/hooks';
|
||||
|
||||
import { api } from '../../../../App';
|
||||
import { EditButton } from '../../../../components/items/EditButton';
|
||||
import { ApiPaths, apiUrl } from '../../../../states/ApiState';
|
||||
import { ApiPaths } from '../../../../enums/ApiEndpoints';
|
||||
import { apiUrl } from '../../../../states/ApiState';
|
||||
import { useUserState } from '../../../../states/UserState';
|
||||
|
||||
export function AccountDetailPanel() {
|
||||
|
@ -19,7 +19,8 @@ import { useEffect, useState } from 'react';
|
||||
|
||||
import { api, queryClient } from '../../../../App';
|
||||
import { PlaceholderPill } from '../../../../components/items/Placeholder';
|
||||
import { ApiPaths, apiUrl } from '../../../../states/ApiState';
|
||||
import { ApiPaths } from '../../../../enums/ApiEndpoints';
|
||||
import { apiUrl } from '../../../../states/ApiState';
|
||||
|
||||
export function SecurityContent() {
|
||||
const [isSsoEnabled, setIsSsoEnabled] = useState<boolean>(false);
|
||||
|
@ -86,10 +86,8 @@ export default function AdminCenter() {
|
||||
<>
|
||||
<Stack spacing="xs">
|
||||
<SettingsHeader
|
||||
title={<Trans>Admin Center</Trans>}
|
||||
subtitle={
|
||||
<Trans>Advanced Amininistrative Options for InvenTree</Trans>
|
||||
}
|
||||
title={t`Admin Center`}
|
||||
subtitle={t`Advanced Amininistrative Options for InvenTree`}
|
||||
switch_link="/settings/system"
|
||||
switch_text="System Settings"
|
||||
/>
|
||||
|
@ -3,11 +3,11 @@ import { LoadingOverlay, Stack } from '@mantine/core';
|
||||
import { IconPlugConnected } from '@tabler/icons-react';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { PageDetail } from '../../../components/nav/PageDetail';
|
||||
import { PanelGroup, PanelType } from '../../../components/nav/PanelGroup';
|
||||
import { SettingsHeader } from '../../../components/nav/SettingsHeader';
|
||||
import { PluginListTable } from '../../../components/tables/plugin/PluginListTable';
|
||||
import { ApiPaths } from '../../../enums/ApiEndpoints';
|
||||
import { useInstance } from '../../../hooks/UseInstance';
|
||||
import { ApiPaths } from '../../../states/ApiState';
|
||||
|
||||
/**
|
||||
* Plugins settings page
|
||||
@ -44,7 +44,7 @@ export default function PluginSettings() {
|
||||
<>
|
||||
<Stack spacing="xs">
|
||||
<LoadingOverlay visible={settingsQuery.isFetching} />
|
||||
<PageDetail title={t`Plugin Settings`} />
|
||||
<SettingsHeader title={t`Plugin Settings`} switch_condition={false} />
|
||||
<PanelGroup pageKey="plugin-settings" panels={pluginPanels} />
|
||||
</Stack>
|
||||
</>
|
||||
|
@ -292,8 +292,8 @@ export default function SystemSettings() {
|
||||
<>
|
||||
<Stack spacing="xs">
|
||||
<SettingsHeader
|
||||
title={server.instance || ''}
|
||||
subtitle={<Trans>System Settings</Trans>}
|
||||
title={t`System Settings`}
|
||||
subtitle={server.instance || ''}
|
||||
switch_link="/settings/user"
|
||||
switch_text={<Trans>Switch to User Setting</Trans>}
|
||||
/>
|
||||
|
@ -112,9 +112,9 @@ export default function UserSettings() {
|
||||
<>
|
||||
<Stack spacing="xs">
|
||||
<SettingsHeader
|
||||
title={`${user?.first_name} ${user?.last_name}`}
|
||||
title={t`Account Settings`}
|
||||
subtitle={`${user?.first_name} ${user?.last_name}`}
|
||||
shorthand={user?.username || ''}
|
||||
subtitle={<Trans>Account Settings</Trans>}
|
||||
switch_link="/settings/system"
|
||||
switch_text={<Trans>Switch to System Setting</Trans>}
|
||||
switch_condition={user?.is_staff || false}
|
||||
|
@ -13,8 +13,9 @@ import { api } from '../App';
|
||||
import { PageDetail } from '../components/nav/PageDetail';
|
||||
import { PanelGroup } from '../components/nav/PanelGroup';
|
||||
import { NotificationTable } from '../components/tables/notifications/NotificationsTable';
|
||||
import { ApiPaths } from '../enums/ApiEndpoints';
|
||||
import { useTableRefresh } from '../hooks/TableRefresh';
|
||||
import { ApiPaths, apiUrl } from '../states/ApiState';
|
||||
import { apiUrl } from '../states/ApiState';
|
||||
|
||||
export default function NotificationsPage() {
|
||||
const unreadRefresh = useTableRefresh('unreadnotifications');
|
||||
|
@ -14,8 +14,7 @@ import {
|
||||
IconPaperclip,
|
||||
IconPrinter,
|
||||
IconQrcode,
|
||||
IconSitemap,
|
||||
IconTrash
|
||||
IconSitemap
|
||||
} from '@tabler/icons-react';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
@ -31,16 +30,17 @@ import {
|
||||
} from '../../components/items/ActionDropdown';
|
||||
import { PageDetail } from '../../components/nav/PageDetail';
|
||||
import { PanelGroup, PanelType } from '../../components/nav/PanelGroup';
|
||||
import { ModelType } from '../../components/render/ModelType';
|
||||
import { StatusRenderer } from '../../components/renderers/StatusRenderer';
|
||||
import { StatusRenderer } from '../../components/render/StatusRenderer';
|
||||
import { BuildOrderTable } from '../../components/tables/build/BuildOrderTable';
|
||||
import { AttachmentTable } from '../../components/tables/general/AttachmentTable';
|
||||
import { StockItemTable } from '../../components/tables/stock/StockItemTable';
|
||||
import { NotesEditor } from '../../components/widgets/MarkdownEditor';
|
||||
import { ApiPaths } from '../../enums/ApiEndpoints';
|
||||
import { ModelType } from '../../enums/ModelType';
|
||||
import { buildOrderFields } from '../../forms/BuildForms';
|
||||
import { openEditApiForm } from '../../functions/forms';
|
||||
import { useInstance } from '../../hooks/UseInstance';
|
||||
import { ApiPaths, apiUrl } from '../../states/ApiState';
|
||||
import { apiUrl } from '../../states/ApiState';
|
||||
import { useUserState } from '../../states/UserState';
|
||||
|
||||
/**
|
||||
|
@ -5,9 +5,9 @@ import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { PageDetail } from '../../components/nav/PageDetail';
|
||||
import { BuildOrderTable } from '../../components/tables/build/BuildOrderTable';
|
||||
import { ApiPaths } from '../../enums/ApiEndpoints';
|
||||
import { buildOrderFields } from '../../forms/BuildForms';
|
||||
import { openCreateApiForm } from '../../functions/forms';
|
||||
import { ApiPaths } from '../../states/ApiState';
|
||||
|
||||
/**
|
||||
* Build Order index page
|
||||
|
@ -33,10 +33,12 @@ import { ReturnOrderTable } from '../../components/tables/sales/ReturnOrderTable
|
||||
import { SalesOrderTable } from '../../components/tables/sales/SalesOrderTable';
|
||||
import { StockItemTable } from '../../components/tables/stock/StockItemTable';
|
||||
import { NotesEditor } from '../../components/widgets/MarkdownEditor';
|
||||
import { ApiPaths } from '../../enums/ApiEndpoints';
|
||||
import { UserRoles } from '../../enums/Roles';
|
||||
import { editCompany } from '../../forms/CompanyForms';
|
||||
import { useInstance } from '../../hooks/UseInstance';
|
||||
import { ApiPaths, apiUrl } from '../../states/ApiState';
|
||||
import { UserRoles, useUserState } from '../../states/UserState';
|
||||
import { apiUrl } from '../../states/ApiState';
|
||||
import { useUserState } from '../../states/UserState';
|
||||
|
||||
export type CompanyDetailProps = {
|
||||
title: string;
|
||||
|
@ -14,8 +14,8 @@ import { PanelGroup, PanelType } from '../../components/nav/PanelGroup';
|
||||
import { PartCategoryTree } from '../../components/nav/PartCategoryTree';
|
||||
import { PartCategoryTable } from '../../components/tables/part/PartCategoryTable';
|
||||
import { PartListTable } from '../../components/tables/part/PartTable';
|
||||
import { ApiPaths } from '../../enums/ApiEndpoints';
|
||||
import { useInstance } from '../../hooks/UseInstance';
|
||||
import { ApiPaths } from '../../states/ApiState';
|
||||
|
||||
/**
|
||||
* Detail view for a single PartCategory instance.
|
||||
|
@ -50,9 +50,10 @@ import { SupplierPartTable } from '../../components/tables/purchasing/SupplierPa
|
||||
import { SalesOrderTable } from '../../components/tables/sales/SalesOrderTable';
|
||||
import { StockItemTable } from '../../components/tables/stock/StockItemTable';
|
||||
import { NotesEditor } from '../../components/widgets/MarkdownEditor';
|
||||
import { ApiPaths } from '../../enums/ApiEndpoints';
|
||||
import { editPart } from '../../forms/PartForms';
|
||||
import { useInstance } from '../../hooks/UseInstance';
|
||||
import { ApiPaths, apiUrl } from '../../states/ApiState';
|
||||
import { apiUrl } from '../../states/ApiState';
|
||||
import { useUserState } from '../../states/UserState';
|
||||
|
||||
/**
|
||||
|
@ -26,8 +26,9 @@ import { AttachmentTable } from '../../components/tables/general/AttachmentTable
|
||||
import { PurchaseOrderLineItemTable } from '../../components/tables/purchasing/PurchaseOrderLineItemTable';
|
||||
import { StockItemTable } from '../../components/tables/stock/StockItemTable';
|
||||
import { NotesEditor } from '../../components/widgets/MarkdownEditor';
|
||||
import { ApiPaths } from '../../enums/ApiEndpoints';
|
||||
import { useInstance } from '../../hooks/UseInstance';
|
||||
import { ApiPaths, apiUrl } from '../../states/ApiState';
|
||||
import { apiUrl } from '../../states/ApiState';
|
||||
import { useUserState } from '../../states/UserState';
|
||||
|
||||
/**
|
||||
@ -114,7 +115,7 @@ export default function PurchaseOrderDetail() {
|
||||
]}
|
||||
/>,
|
||||
<ActionDropdown
|
||||
key="order"
|
||||
key="order-actions"
|
||||
tooltip={t`Order Actions`}
|
||||
icon={<IconDots />}
|
||||
actions={[EditItemAction({}), DeleteItemAction({})]}
|
||||
|
@ -8,8 +8,9 @@ import { PageDetail } from '../../components/nav/PageDetail';
|
||||
import { PanelGroup, PanelType } from '../../components/nav/PanelGroup';
|
||||
import { AttachmentTable } from '../../components/tables/general/AttachmentTable';
|
||||
import { NotesEditor } from '../../components/widgets/MarkdownEditor';
|
||||
import { ApiPaths } from '../../enums/ApiEndpoints';
|
||||
import { useInstance } from '../../hooks/UseInstance';
|
||||
import { ApiPaths, apiUrl } from '../../states/ApiState';
|
||||
import { apiUrl } from '../../states/ApiState';
|
||||
|
||||
/**
|
||||
* Detail page for a single ReturnOrder
|
||||
|
@ -16,8 +16,9 @@ import { PageDetail } from '../../components/nav/PageDetail';
|
||||
import { PanelGroup, PanelType } from '../../components/nav/PanelGroup';
|
||||
import { AttachmentTable } from '../../components/tables/general/AttachmentTable';
|
||||
import { NotesEditor } from '../../components/widgets/MarkdownEditor';
|
||||
import { ApiPaths } from '../../enums/ApiEndpoints';
|
||||
import { useInstance } from '../../hooks/UseInstance';
|
||||
import { ApiPaths, apiUrl } from '../../states/ApiState';
|
||||
import { apiUrl } from '../../states/ApiState';
|
||||
|
||||
/**
|
||||
* Detail page for a single SalesOrder
|
||||
|
@ -9,8 +9,8 @@ import { PanelGroup, PanelType } from '../../components/nav/PanelGroup';
|
||||
import { StockLocationTree } from '../../components/nav/StockLocationTree';
|
||||
import { StockItemTable } from '../../components/tables/stock/StockItemTable';
|
||||
import { StockLocationTable } from '../../components/tables/stock/StockLocationTable';
|
||||
import { ApiPaths } from '../../enums/ApiEndpoints';
|
||||
import { useInstance } from '../../hooks/UseInstance';
|
||||
import { ApiPaths } from '../../states/ApiState';
|
||||
|
||||
export default function Stock() {
|
||||
const { id } = useParams();
|
||||
|
@ -35,9 +35,10 @@ import { PanelGroup, PanelType } from '../../components/nav/PanelGroup';
|
||||
import { StockLocationTree } from '../../components/nav/StockLocationTree';
|
||||
import { AttachmentTable } from '../../components/tables/general/AttachmentTable';
|
||||
import { NotesEditor } from '../../components/widgets/MarkdownEditor';
|
||||
import { ApiPaths } from '../../enums/ApiEndpoints';
|
||||
import { editStockItem } from '../../forms/StockForms';
|
||||
import { useInstance } from '../../hooks/UseInstance';
|
||||
import { ApiPaths, apiUrl } from '../../states/ApiState';
|
||||
import { apiUrl } from '../../states/ApiState';
|
||||
import { useUserState } from '../../states/UserState';
|
||||
|
||||
export default function StockDetail() {
|
||||
|
@ -2,10 +2,11 @@ import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
|
||||
import { api } from '../App';
|
||||
import { ModelType } from '../components/render/ModelType';
|
||||
import { StatusCodeListInterface } from '../components/renderers/StatusRenderer';
|
||||
import { StatusCodeListInterface } from '../components/render/StatusRenderer';
|
||||
import { statusCodeList } from '../defaults/backendMappings';
|
||||
import { emptyServerAPI } from '../defaults/defaults';
|
||||
import { ApiPaths } from '../enums/ApiEndpoints';
|
||||
import { ModelType } from '../enums/ModelType';
|
||||
import { ServerAPIProps } from './states';
|
||||
|
||||
type StatusLookup = Record<ModelType, StatusCodeListInterface>;
|
||||
@ -48,87 +49,6 @@ export const useServerApiState = create<ServerApiStateProps>()(
|
||||
)
|
||||
);
|
||||
|
||||
export enum ApiPaths {
|
||||
api_server_info = 'api-server-info',
|
||||
|
||||
api_search = 'api-search',
|
||||
|
||||
// User information
|
||||
user_me = 'api-user-me',
|
||||
user_roles = 'api-user-roles',
|
||||
user_token = 'api-user-token',
|
||||
user_simple_login = 'api-user-simple-login',
|
||||
user_reset = 'api-user-reset',
|
||||
user_reset_set = 'api-user-reset-set',
|
||||
user_sso = 'api-user-sso',
|
||||
user_sso_remove = 'api-user-sso-remove',
|
||||
user_emails = 'api-user-emails',
|
||||
user_email_verify = 'api-user-email-verify',
|
||||
user_email_primary = 'api-user-email-primary',
|
||||
user_email_remove = 'api-user-email-remove',
|
||||
|
||||
settings_global_list = 'api-settings-global-list',
|
||||
settings_user_list = 'api-settings-user-list',
|
||||
notifications_list = 'api-notifications-list',
|
||||
|
||||
currency_list = 'api-currency-list',
|
||||
currency_refresh = 'api-currency-refresh',
|
||||
|
||||
barcode = 'api-barcode',
|
||||
news = 'news',
|
||||
global_status = 'api-global-status',
|
||||
version = 'api-version',
|
||||
sso_providers = 'api-sso-providers',
|
||||
|
||||
// Build order URLs
|
||||
build_order_list = 'api-build-list',
|
||||
build_order_attachment_list = 'api-build-attachment-list',
|
||||
|
||||
// BOM URLs
|
||||
bom_list = 'api-bom-list',
|
||||
|
||||
// Part URLs
|
||||
part_list = 'api-part-list',
|
||||
category_list = 'api-category-list',
|
||||
category_tree = 'api-category-tree',
|
||||
related_part_list = 'api-related-part-list',
|
||||
part_attachment_list = 'api-part-attachment-list',
|
||||
part_parameter_list = 'api-part-parameter-list',
|
||||
part_parameter_template_list = 'api-part-parameter-template-list',
|
||||
|
||||
// Company URLs
|
||||
company_list = 'api-company-list',
|
||||
company_attachment_list = 'api-company-attachment-list',
|
||||
supplier_part_list = 'api-supplier-part-list',
|
||||
manufacturer_part_list = 'api-manufacturer-part-list',
|
||||
|
||||
// Stock Item URLs
|
||||
stock_item_list = 'api-stock-item-list',
|
||||
stock_tracking_list = 'api-stock-tracking-list',
|
||||
stock_location_list = 'api-stock-location-list',
|
||||
stock_location_tree = 'api-stock-location-tree',
|
||||
stock_attachment_list = 'api-stock-attachment-list',
|
||||
|
||||
// Purchase Order URLs
|
||||
purchase_order_list = 'api-purchase-order-list',
|
||||
purchase_order_line_list = 'api-purchase-order-line-list',
|
||||
purchase_order_attachment_list = 'api-purchase-order-attachment-list',
|
||||
|
||||
// Sales Order URLs
|
||||
sales_order_list = 'api-sales-order-list',
|
||||
sales_order_attachment_list = 'api-sales-order-attachment-list',
|
||||
|
||||
// Return Order URLs
|
||||
return_order_list = 'api-return-order-list',
|
||||
return_order_attachment_list = 'api-return-order-attachment-list',
|
||||
|
||||
// Plugin URLs
|
||||
plugin_list = 'api-plugin-list',
|
||||
|
||||
project_code_list = 'api-project-code-list',
|
||||
custom_unit_list = 'api-custom-unit-list'
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to return the API prefix.
|
||||
* For now it is fixed, but may be configurable in the future.
|
||||
@ -144,6 +64,10 @@ export function apiEndpoint(path: ApiPaths): string {
|
||||
switch (path) {
|
||||
case ApiPaths.api_server_info:
|
||||
return '';
|
||||
case ApiPaths.user_list:
|
||||
return 'user/';
|
||||
case ApiPaths.owner_list:
|
||||
return 'user/owner/';
|
||||
case ApiPaths.user_me:
|
||||
return 'user/me/';
|
||||
case ApiPaths.user_roles:
|
||||
@ -214,6 +138,10 @@ export function apiEndpoint(path: ApiPaths): string {
|
||||
return 'part/attachment/';
|
||||
case ApiPaths.company_list:
|
||||
return 'company/';
|
||||
case ApiPaths.contact_list:
|
||||
return 'company/contact/';
|
||||
case ApiPaths.address_list:
|
||||
return 'company/address/';
|
||||
case ApiPaths.company_attachment_list:
|
||||
return 'company/attachment/';
|
||||
case ApiPaths.supplier_part_list:
|
||||
@ -240,6 +168,8 @@ export function apiEndpoint(path: ApiPaths): string {
|
||||
return 'order/so/';
|
||||
case ApiPaths.sales_order_attachment_list:
|
||||
return 'order/so/attachment/';
|
||||
case ApiPaths.sales_order_shipment_list:
|
||||
return 'order/so/shipment/';
|
||||
case ApiPaths.return_order_list:
|
||||
return 'order/ro/';
|
||||
case ApiPaths.return_order_attachment_list:
|
||||
|
@ -4,7 +4,9 @@
|
||||
import { create } from 'zustand';
|
||||
|
||||
import { api } from '../App';
|
||||
import { ApiPaths, apiUrl } from './ApiState';
|
||||
import { ApiPaths } from '../enums/ApiEndpoints';
|
||||
import { isTrue } from '../functions/conversion';
|
||||
import { apiUrl } from './ApiState';
|
||||
import { Setting, SettingsLookup } from './states';
|
||||
|
||||
export interface SettingsStateProps {
|
||||
@ -12,6 +14,8 @@ export interface SettingsStateProps {
|
||||
lookup: SettingsLookup;
|
||||
fetchSettings: () => void;
|
||||
endpoint: ApiPaths;
|
||||
getSetting: (key: string, default_value?: string) => string; // Return a raw setting value
|
||||
isSet: (key: string, default_value?: boolean) => boolean; // Check a "boolean" setting
|
||||
}
|
||||
|
||||
/**
|
||||
@ -34,6 +38,13 @@ export const useGlobalSettingsState = create<SettingsStateProps>(
|
||||
.catch((error) => {
|
||||
console.error('Error fetching global settings:', error);
|
||||
});
|
||||
},
|
||||
getSetting: (key: string, default_value?: string) => {
|
||||
return get().lookup[key] ?? default_value ?? '';
|
||||
},
|
||||
isSet: (key: string, default_value?: boolean) => {
|
||||
let value = get().lookup[key] ?? default_value ?? 'false';
|
||||
return isTrue(value);
|
||||
}
|
||||
})
|
||||
);
|
||||
@ -57,6 +68,13 @@ export const useUserSettingsState = create<SettingsStateProps>((set, get) => ({
|
||||
.catch((error) => {
|
||||
console.error('Error fetching user settings:', error);
|
||||
});
|
||||
},
|
||||
getSetting: (key: string, default_value?: string) => {
|
||||
return get().lookup[key] ?? default_value ?? '';
|
||||
},
|
||||
isSet: (key: string, default_value?: boolean) => {
|
||||
let value = get().lookup[key] ?? default_value ?? 'false';
|
||||
return isTrue(value);
|
||||
}
|
||||
}));
|
||||
|
||||
|
@ -1,36 +1,12 @@
|
||||
import { create } from 'zustand';
|
||||
|
||||
import { api } from '../App';
|
||||
import { ApiPaths } from '../enums/ApiEndpoints';
|
||||
import { UserPermissions, UserRoles } from '../enums/Roles';
|
||||
import { doClassicLogout } from '../functions/auth';
|
||||
import { ApiPaths, apiUrl } from './ApiState';
|
||||
import { apiUrl } from './ApiState';
|
||||
import { UserProps } from './states';
|
||||
|
||||
/*
|
||||
* Enumeration of available user role groups
|
||||
*/
|
||||
export enum UserRoles {
|
||||
admin = 'admin',
|
||||
build = 'build',
|
||||
part = 'part',
|
||||
part_category = 'part_category',
|
||||
purchase_order = 'purchase_order',
|
||||
return_order = 'return_order',
|
||||
sales_order = 'sales_order',
|
||||
stock = 'stock',
|
||||
stock_location = 'stocklocation',
|
||||
stocktake = 'stocktake'
|
||||
}
|
||||
|
||||
/*
|
||||
* Enumeration of available user permissions within each role group
|
||||
*/
|
||||
export enum UserPermissions {
|
||||
view = 'view',
|
||||
add = 'add',
|
||||
change = 'change',
|
||||
delete = 'delete'
|
||||
}
|
||||
|
||||
interface UserStateProps {
|
||||
user: UserProps | undefined;
|
||||
username: () => string;
|
||||
@ -86,9 +62,9 @@ export const useUserState = create<UserStateProps>((set, get) => ({
|
||||
const user: UserProps = get().user as UserProps;
|
||||
|
||||
// Update user with role data
|
||||
user.roles = response.data.roles;
|
||||
user.is_staff = response.data.is_staff ?? false;
|
||||
user.is_superuser = response.data.is_superuser ?? false;
|
||||
user.roles = response.data?.roles ?? {};
|
||||
user.is_staff = response.data?.is_staff ?? false;
|
||||
user.is_superuser = response.data?.is_superuser ?? false;
|
||||
set({ user: user });
|
||||
})
|
||||
.catch((error) => {
|
||||
@ -99,11 +75,15 @@ export const useUserState = create<UserStateProps>((set, get) => ({
|
||||
// Check if the user has the specified permission for the specified role
|
||||
const user: UserProps = get().user as UserProps;
|
||||
|
||||
if (user.is_superuser) return true;
|
||||
if (user.roles === undefined) return false;
|
||||
if (user.roles[role] === undefined) return false;
|
||||
if (!user) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return user.roles[role].includes(permission);
|
||||
if (user?.is_superuser) return true;
|
||||
if (user?.roles === undefined) return false;
|
||||
if (user?.roles[role] === undefined) return false;
|
||||
|
||||
return user?.roles[role].includes(permission);
|
||||
},
|
||||
hasDeleteRole: (role: UserRoles) => {
|
||||
return get().checkUserRole(role, UserPermissions.delete);
|
||||
|
Loading…
Reference in New Issue
Block a user