mirror of
https://github.com/inventree/InvenTree.git
synced 2025-12-16 09:18:10 +00:00
Implement generic "parameter" table
This commit is contained in:
@@ -37,7 +37,11 @@ from data_exporter.mixins import DataExportViewMixin
|
||||
from generic.states.api import urlpattern as generic_states_api_urls
|
||||
from InvenTree.api import BulkDeleteMixin, MetadataView
|
||||
from InvenTree.config import CONFIG_LOOKUPS
|
||||
from InvenTree.filters import ORDER_FILTER, SEARCH_ORDER_FILTER
|
||||
from InvenTree.filters import (
|
||||
ORDER_FILTER,
|
||||
SEARCH_ORDER_FILTER,
|
||||
SEARCH_ORDER_FILTER_ALIAS,
|
||||
)
|
||||
from InvenTree.helpers import inheritors, str2bool
|
||||
from InvenTree.helpers_email import send_email
|
||||
from InvenTree.mixins import (
|
||||
@@ -837,6 +841,10 @@ class ParameterFilter(FilterSet):
|
||||
model = common.models.Parameter
|
||||
fields = ['model_type', 'model_id', 'template', 'updated_by']
|
||||
|
||||
enabled = rest_filters.BooleanFilter(
|
||||
label='Template Enabled', field_name='template__enabled'
|
||||
)
|
||||
|
||||
|
||||
class ParameterMixin:
|
||||
"""Mixin class for Parameter views."""
|
||||
@@ -856,7 +864,7 @@ class ParameterList(
|
||||
"""List API endpoint for Parameter objects."""
|
||||
|
||||
filterset_class = ParameterFilter
|
||||
filter_backends = SEARCH_ORDER_FILTER
|
||||
filter_backends = SEARCH_ORDER_FILTER_ALIAS
|
||||
|
||||
ordering_fields = ['name', 'data', 'units', 'template', 'updated', 'updated_by']
|
||||
|
||||
|
||||
@@ -33,6 +33,11 @@ export const ModelInformationDict: ModelDict = {
|
||||
admin_url: '/part/part/',
|
||||
icon: 'part'
|
||||
},
|
||||
parametertemplate: {
|
||||
label: () => t`Parameter Template`,
|
||||
label_multiple: () => t`Parameter Templates`,
|
||||
api_endpoint: ApiEndpoints.parameter_template_list
|
||||
},
|
||||
partparametertemplate: {
|
||||
label: () => t`Part Parameter Template`,
|
||||
label_multiple: () => t`Part Parameter Templates`,
|
||||
|
||||
@@ -17,6 +17,7 @@ export enum ModelType {
|
||||
buildline = 'buildline',
|
||||
builditem = 'builditem',
|
||||
company = 'company',
|
||||
parametertemplate = 'parametertemplate',
|
||||
purchaseorder = 'purchaseorder',
|
||||
purchaseorderlineitem = 'purchaseorderlineitem',
|
||||
salesorder = 'salesorder',
|
||||
|
||||
@@ -2,6 +2,18 @@ import type { ReactNode } from 'react';
|
||||
|
||||
import { type InstanceRenderInterface, RenderInlineModel } from './Instance';
|
||||
|
||||
export function RenderParameterTemplate({
|
||||
instance
|
||||
}: Readonly<InstanceRenderInterface>): ReactNode {
|
||||
return (
|
||||
<RenderInlineModel
|
||||
primary={instance.name}
|
||||
secondary={instance.description}
|
||||
suffix={instance.units}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function RenderProjectCode({
|
||||
instance
|
||||
}: Readonly<InstanceRenderInterface>): ReactNode {
|
||||
|
||||
@@ -36,6 +36,7 @@ import {
|
||||
RenderContentType,
|
||||
RenderError,
|
||||
RenderImportSession,
|
||||
RenderParameterTemplate,
|
||||
RenderProjectCode,
|
||||
RenderSelectionList
|
||||
} from './Generic';
|
||||
@@ -71,6 +72,7 @@ export const RendererLookup: ModelRendererDict = {
|
||||
[ModelType.builditem]: RenderBuildItem,
|
||||
[ModelType.company]: RenderCompany,
|
||||
[ModelType.contact]: RenderContact,
|
||||
[ModelType.parametertemplate]: RenderParameterTemplate,
|
||||
[ModelType.manufacturerpart]: RenderManufacturerPart,
|
||||
[ModelType.owner]: RenderOwner,
|
||||
[ModelType.part]: RenderPart,
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
import { IconUsers } from '@tabler/icons-react';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
|
||||
import type { ModelType } from '@lib/enums/ModelType';
|
||||
import { apiUrl } from '@lib/functions/Api';
|
||||
import type { ApiFormFieldSet } from '@lib/types/Forms';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import type {
|
||||
StatusCodeInterface,
|
||||
StatusCodeListInterface
|
||||
} from '../components/render/StatusRenderer';
|
||||
import { useApi } from '../contexts/ApiContext';
|
||||
import { useGlobalStatusState } from '../states/GlobalStatusState';
|
||||
|
||||
export function projectCodeFields(): ApiFormFieldSet {
|
||||
@@ -91,3 +95,117 @@ export function extraLineItemFields(): ApiFormFieldSet {
|
||||
link: {}
|
||||
};
|
||||
}
|
||||
|
||||
export function useParameterFields({
|
||||
modelType,
|
||||
modelId
|
||||
}: {
|
||||
modelType: ModelType;
|
||||
modelId: number;
|
||||
}): ApiFormFieldSet {
|
||||
const api = useApi();
|
||||
|
||||
// Valid field choices
|
||||
const [choices, setChoices] = useState<any[]>([]);
|
||||
|
||||
// Field type for "data" input
|
||||
const [fieldType, setFieldType] = useState<'string' | 'boolean' | 'choice'>(
|
||||
'string'
|
||||
);
|
||||
|
||||
const [data, setData] = useState<string>('');
|
||||
|
||||
// Reset the field type and choices when the model changes
|
||||
useEffect(() => {
|
||||
setFieldType('string');
|
||||
setChoices([]);
|
||||
setData('');
|
||||
}, [modelType, modelId]);
|
||||
|
||||
return useMemo(() => {
|
||||
return {
|
||||
model_type: {
|
||||
hidden: true,
|
||||
value: modelType
|
||||
},
|
||||
model_id: {
|
||||
hidden: true,
|
||||
value: modelId
|
||||
},
|
||||
template: {
|
||||
filters: {
|
||||
model_type: modelType,
|
||||
enabled: true
|
||||
},
|
||||
onValueChange: (value: any, record: any) => {
|
||||
// Adjust the type of the "data" field based on the selected template
|
||||
if (record?.checkbox) {
|
||||
// This is a "checkbox" field
|
||||
setChoices([]);
|
||||
setFieldType('boolean');
|
||||
} else if (record?.choices) {
|
||||
const _choices: string[] = record.choices.split(',');
|
||||
|
||||
if (_choices.length > 0) {
|
||||
setChoices(
|
||||
_choices.map((choice) => {
|
||||
return {
|
||||
display_name: choice.trim(),
|
||||
value: choice.trim()
|
||||
};
|
||||
})
|
||||
);
|
||||
setFieldType('choice');
|
||||
} else {
|
||||
setChoices([]);
|
||||
setFieldType('string');
|
||||
}
|
||||
} else if (record?.selectionlist) {
|
||||
api
|
||||
.get(
|
||||
apiUrl(ApiEndpoints.selectionlist_detail, record.selectionlist)
|
||||
)
|
||||
.then((res) => {
|
||||
setChoices(
|
||||
res.data.choices.map((item: any) => {
|
||||
return {
|
||||
value: item.value,
|
||||
display_name: item.label
|
||||
};
|
||||
})
|
||||
);
|
||||
setFieldType('choice');
|
||||
});
|
||||
} else {
|
||||
setChoices([]);
|
||||
setFieldType('string');
|
||||
}
|
||||
}
|
||||
},
|
||||
data: {
|
||||
value: data,
|
||||
onValueChange: (value: any) => {
|
||||
setData(value);
|
||||
},
|
||||
type: fieldType,
|
||||
field_type: fieldType,
|
||||
choices: fieldType === 'choice' ? choices : undefined,
|
||||
default: fieldType === 'boolean' ? false : undefined,
|
||||
adjustValue: (value: any) => {
|
||||
// Coerce boolean value into a string (required by backend)
|
||||
|
||||
let v: string = value.toString().trim();
|
||||
|
||||
if (fieldType === 'boolean') {
|
||||
if (v.toLowerCase() !== 'true') {
|
||||
v = 'false';
|
||||
}
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
},
|
||||
note: {}
|
||||
};
|
||||
}, [data, modelType, fieldType, choices, modelId]);
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@ import { useUserState } from '../../states/UserState';
|
||||
import { BomTable } from '../../tables/bom/BomTable';
|
||||
import { UsedInTable } from '../../tables/bom/UsedInTable';
|
||||
import { BuildOrderTable } from '../../tables/build/BuildOrderTable';
|
||||
import { PartParameterTable } from '../../tables/part/PartParameterTable';
|
||||
import { ParameterTable } from '../../tables/general/ParameterTable';
|
||||
import PartPurchaseOrdersTable from '../../tables/part/PartPurchaseOrdersTable';
|
||||
import PartTestResultTable from '../../tables/part/PartTestResultTable';
|
||||
import PartTestTemplateTable from '../../tables/part/PartTestTemplateTable';
|
||||
@@ -792,11 +792,10 @@ export default function PartDetail() {
|
||||
name: 'parameters',
|
||||
label: t`Parameters`,
|
||||
icon: <IconList />,
|
||||
content: (
|
||||
<PartParameterTable
|
||||
partId={id ?? -1}
|
||||
partLocked={part?.locked == true}
|
||||
/>
|
||||
content: part?.pk ? (
|
||||
<ParameterTable modelType={ModelType.part} modelId={part?.pk} />
|
||||
) : (
|
||||
<Skeleton />
|
||||
)
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,10 +1,34 @@
|
||||
import { ApiEndpoints, type ModelType, apiUrl } from '@lib/index';
|
||||
import {
|
||||
AddItemButton,
|
||||
ApiEndpoints,
|
||||
type ModelType,
|
||||
RowDeleteAction,
|
||||
RowEditAction,
|
||||
YesNoButton,
|
||||
apiUrl,
|
||||
formatDecimal
|
||||
} from '@lib/index';
|
||||
import type { TableFilter } from '@lib/types/Filters';
|
||||
import type { TableColumn } from '@lib/types/Tables';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useParameterFields } from '../../forms/CommonForms';
|
||||
import {
|
||||
useCreateApiFormModal,
|
||||
useDeleteApiFormModal,
|
||||
useEditApiFormModal
|
||||
} from '../../hooks/UseForm';
|
||||
import { useTable } from '../../hooks/UseTable';
|
||||
import { useUserState } from '../../states/UserState';
|
||||
import {
|
||||
DateColumn,
|
||||
DescriptionColumn,
|
||||
NoteColumn,
|
||||
UserColumn
|
||||
} from '../ColumnRenderers';
|
||||
import { UserFilter } from '../Filter';
|
||||
import { InvenTreeTable } from '../InvenTreeTable';
|
||||
import { TableHoverCard } from '../TableHoverCard';
|
||||
|
||||
/**
|
||||
* Construct a table listing parameters
|
||||
@@ -20,32 +44,159 @@ export function ParameterTable({
|
||||
const user = useUserState();
|
||||
|
||||
const tableColumns: TableColumn[] = useMemo(() => {
|
||||
// TODO
|
||||
return [];
|
||||
return [
|
||||
{
|
||||
accessor: 'template_detail.name',
|
||||
switchable: false,
|
||||
sortable: true,
|
||||
ordering: 'name'
|
||||
},
|
||||
DescriptionColumn({
|
||||
accessor: 'template_detail.description'
|
||||
}),
|
||||
{
|
||||
accessor: 'data',
|
||||
switchable: false,
|
||||
sortable: true,
|
||||
render: (record) => {
|
||||
const template = record.template_detail;
|
||||
|
||||
if (template?.checkbox) {
|
||||
return <YesNoButton value={record.data} />;
|
||||
}
|
||||
|
||||
const extra: any[] = [];
|
||||
|
||||
if (
|
||||
template.units &&
|
||||
record.data_numeric &&
|
||||
record.data_numeric != record.data
|
||||
) {
|
||||
const numeric = formatDecimal(record.data_numeric, { digits: 15 });
|
||||
extra.push(`${numeric} [${template.units}]`);
|
||||
}
|
||||
|
||||
return (
|
||||
<TableHoverCard
|
||||
value={record.data}
|
||||
extra={extra}
|
||||
title={t`Internal Units`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
accessor: 'template_detail.units',
|
||||
ordering: 'units',
|
||||
sortable: true
|
||||
},
|
||||
NoteColumn({}),
|
||||
DateColumn({
|
||||
accessor: 'updated',
|
||||
title: t`Last Updated`,
|
||||
sortable: true,
|
||||
switchable: true
|
||||
}),
|
||||
UserColumn({
|
||||
accessor: 'updated_by_detail',
|
||||
ordering: 'updated_by',
|
||||
title: t`Updated By`
|
||||
})
|
||||
];
|
||||
}, [user]);
|
||||
|
||||
const tableFilters: TableFilter[] = useMemo(() => {
|
||||
// TODO
|
||||
return [];
|
||||
return [
|
||||
{
|
||||
name: 'enabled',
|
||||
label: 'Enabled',
|
||||
description: t`Show parameters for enabled templates`,
|
||||
type: 'boolean'
|
||||
},
|
||||
UserFilter({
|
||||
name: 'updated_by',
|
||||
label: t`Updated By`,
|
||||
description: t`Filter by user who last updated the parameter`
|
||||
})
|
||||
];
|
||||
}, []);
|
||||
|
||||
const [selectedParameter, setSelectedParameter] = useState<any | undefined>(
|
||||
undefined
|
||||
);
|
||||
|
||||
const newParameter = useCreateApiFormModal({
|
||||
url: ApiEndpoints.parameter_list,
|
||||
title: t`Add Parameter`,
|
||||
fields: useParameterFields({ modelType, modelId }),
|
||||
table: table
|
||||
});
|
||||
|
||||
const editParameter = useEditApiFormModal({
|
||||
url: ApiEndpoints.parameter_list,
|
||||
pk: selectedParameter?.pk,
|
||||
title: t`Edit Parameter`,
|
||||
fields: useParameterFields({ modelType, modelId }),
|
||||
table: table
|
||||
});
|
||||
|
||||
const deleteParameter = useDeleteApiFormModal({
|
||||
url: ApiEndpoints.parameter_list,
|
||||
pk: selectedParameter?.pk,
|
||||
title: t`Delete Parameter`,
|
||||
table: table
|
||||
});
|
||||
|
||||
const tableActions = useMemo(() => {
|
||||
// TODO
|
||||
return [];
|
||||
return [
|
||||
<AddItemButton
|
||||
key='add-parameter'
|
||||
hidden={!user.hasAddPermission(modelType)}
|
||||
onClick={() => {
|
||||
setSelectedParameter(undefined);
|
||||
newParameter.open();
|
||||
}}
|
||||
/>
|
||||
];
|
||||
}, [user]);
|
||||
|
||||
const rowActions = useCallback(() => {
|
||||
return [];
|
||||
}, [user]);
|
||||
const rowActions = useCallback(
|
||||
(record: any) => {
|
||||
return [
|
||||
RowEditAction({
|
||||
tooltip: t`Edit Parameter`,
|
||||
onClick: () => {
|
||||
setSelectedParameter(record);
|
||||
editParameter.open();
|
||||
},
|
||||
hidden: !user.hasChangePermission(modelType)
|
||||
}),
|
||||
RowDeleteAction({
|
||||
tooltip: t`Delete Parameter`,
|
||||
onClick: () => {
|
||||
setSelectedParameter(record);
|
||||
deleteParameter.open();
|
||||
},
|
||||
hidden: !user.hasDeletePermission(modelType)
|
||||
})
|
||||
];
|
||||
},
|
||||
[user]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{newParameter.modal}
|
||||
{editParameter.modal}
|
||||
{deleteParameter.modal}
|
||||
<InvenTreeTable
|
||||
url={apiUrl(ApiEndpoints.parameter_list)}
|
||||
tableState={table}
|
||||
columns={tableColumns}
|
||||
props={{
|
||||
enableDownload: true,
|
||||
enableBulkDelete: true,
|
||||
enableSelection: true,
|
||||
rowActions: rowActions,
|
||||
tableActions: tableActions,
|
||||
tableFilters: tableFilters,
|
||||
|
||||
@@ -1,254 +0,0 @@
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { Alert, Stack, Text } from '@mantine/core';
|
||||
import { IconLock } from '@tabler/icons-react';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
|
||||
import { AddItemButton } from '@lib/components/AddItemButton';
|
||||
import {
|
||||
type RowAction,
|
||||
RowDeleteAction,
|
||||
RowEditAction
|
||||
} from '@lib/components/RowActions';
|
||||
import { YesNoButton } from '@lib/components/YesNoButton';
|
||||
import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
|
||||
import { UserRoles } from '@lib/enums/Roles';
|
||||
import { apiUrl } from '@lib/functions/Api';
|
||||
import type { TableFilter } from '@lib/types/Filters';
|
||||
import type { ApiFormFieldSet } from '@lib/types/Forms';
|
||||
import type { TableColumn } from '@lib/types/Tables';
|
||||
import { formatDecimal } from '../../defaults/formatters';
|
||||
import { usePartParameterFields } from '../../forms/PartForms';
|
||||
import {
|
||||
useCreateApiFormModal,
|
||||
useDeleteApiFormModal,
|
||||
useEditApiFormModal
|
||||
} from '../../hooks/UseForm';
|
||||
import { useTable } from '../../hooks/UseTable';
|
||||
import { useUserState } from '../../states/UserState';
|
||||
import {
|
||||
DateColumn,
|
||||
DescriptionColumn,
|
||||
NoteColumn,
|
||||
PartColumn,
|
||||
UserColumn
|
||||
} from '../ColumnRenderers';
|
||||
import { IncludeVariantsFilter, UserFilter } from '../Filter';
|
||||
import { InvenTreeTable } from '../InvenTreeTable';
|
||||
import { TableHoverCard } from '../TableHoverCard';
|
||||
|
||||
/**
|
||||
* Construct a table listing parameters for a given part
|
||||
*/
|
||||
export function PartParameterTable({
|
||||
partId,
|
||||
partLocked
|
||||
}: Readonly<{
|
||||
partId: any;
|
||||
partLocked?: boolean;
|
||||
}>) {
|
||||
const table = useTable('part-parameters');
|
||||
|
||||
const user = useUserState();
|
||||
|
||||
const tableColumns: TableColumn[] = useMemo(() => {
|
||||
return [
|
||||
PartColumn({
|
||||
part: 'part_detail'
|
||||
}),
|
||||
{
|
||||
accessor: 'part_detail.IPN',
|
||||
sortable: false,
|
||||
switchable: true,
|
||||
defaultVisible: false
|
||||
},
|
||||
{
|
||||
accessor: 'template_detail.name',
|
||||
switchable: false,
|
||||
sortable: true,
|
||||
ordering: 'name',
|
||||
render: (record) => {
|
||||
const variant = String(partId) != String(record.part);
|
||||
|
||||
return (
|
||||
<Text style={{ fontStyle: variant ? 'italic' : 'inherit' }}>
|
||||
{record.template_detail?.name}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
},
|
||||
DescriptionColumn({
|
||||
accessor: 'template_detail.description'
|
||||
}),
|
||||
{
|
||||
accessor: 'data',
|
||||
switchable: false,
|
||||
sortable: true,
|
||||
render: (record) => {
|
||||
const template = record.template_detail;
|
||||
|
||||
if (template?.checkbox) {
|
||||
return <YesNoButton value={record.data} />;
|
||||
}
|
||||
|
||||
const extra: any[] = [];
|
||||
|
||||
if (
|
||||
template.units &&
|
||||
record.data_numeric &&
|
||||
record.data_numeric != record.data
|
||||
) {
|
||||
const numeric = formatDecimal(record.data_numeric, { digits: 15 });
|
||||
extra.push(`${numeric} [${template.units}]`);
|
||||
}
|
||||
|
||||
return (
|
||||
<TableHoverCard
|
||||
value={record.data}
|
||||
extra={extra}
|
||||
title={t`Internal Units`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
accessor: 'template_detail.units',
|
||||
ordering: 'units',
|
||||
sortable: true
|
||||
},
|
||||
NoteColumn({}),
|
||||
DateColumn({
|
||||
accessor: 'updated',
|
||||
title: t`Last Updated`,
|
||||
sortable: true,
|
||||
switchable: true
|
||||
}),
|
||||
UserColumn({
|
||||
accessor: 'updated_by_detail',
|
||||
ordering: 'updated_by',
|
||||
title: t`Updated By`
|
||||
})
|
||||
];
|
||||
}, [partId]);
|
||||
|
||||
const tableFilters: TableFilter[] = useMemo(() => {
|
||||
return [
|
||||
IncludeVariantsFilter(),
|
||||
UserFilter({
|
||||
name: 'updated_by',
|
||||
label: t`Updated By`,
|
||||
description: t`Filter by user who last updated the parameter`
|
||||
})
|
||||
];
|
||||
}, []);
|
||||
|
||||
const partParameterFields: ApiFormFieldSet = usePartParameterFields({});
|
||||
|
||||
const newParameter = useCreateApiFormModal({
|
||||
url: ApiEndpoints.part_parameter_list,
|
||||
title: t`New Part Parameter`,
|
||||
fields: useMemo(() => ({ ...partParameterFields }), [partParameterFields]),
|
||||
focus: 'template',
|
||||
initialData: {
|
||||
part: partId
|
||||
},
|
||||
table: table
|
||||
});
|
||||
|
||||
const [selectedParameter, setSelectedParameter] = useState<
|
||||
number | undefined
|
||||
>(undefined);
|
||||
|
||||
const editParameter = useEditApiFormModal({
|
||||
url: ApiEndpoints.part_parameter_list,
|
||||
pk: selectedParameter,
|
||||
title: t`Edit Part Parameter`,
|
||||
focus: 'data',
|
||||
fields: useMemo(() => ({ ...partParameterFields }), [partParameterFields]),
|
||||
table: table
|
||||
});
|
||||
|
||||
const deleteParameter = useDeleteApiFormModal({
|
||||
url: ApiEndpoints.part_parameter_list,
|
||||
pk: selectedParameter,
|
||||
title: t`Delete Part Parameter`,
|
||||
table: table
|
||||
});
|
||||
|
||||
// Callback for row actions
|
||||
const rowActions = useCallback(
|
||||
(record: any): RowAction[] => {
|
||||
// Actions not allowed for "variant" rows
|
||||
if (String(partId) != String(record.part)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
RowEditAction({
|
||||
tooltip: t`Edit Part Parameter`,
|
||||
hidden: partLocked || !user.hasChangeRole(UserRoles.part),
|
||||
onClick: () => {
|
||||
setSelectedParameter(record.pk);
|
||||
editParameter.open();
|
||||
}
|
||||
}),
|
||||
RowDeleteAction({
|
||||
tooltip: t`Delete Part Parameter`,
|
||||
hidden: partLocked || !user.hasDeleteRole(UserRoles.part),
|
||||
onClick: () => {
|
||||
setSelectedParameter(record.pk);
|
||||
deleteParameter.open();
|
||||
}
|
||||
})
|
||||
];
|
||||
},
|
||||
[partId, partLocked, user]
|
||||
);
|
||||
|
||||
// Custom table actions
|
||||
const tableActions = useMemo(() => {
|
||||
return [
|
||||
<AddItemButton
|
||||
key='add-parameter'
|
||||
hidden={partLocked || !user.hasAddRole(UserRoles.part)}
|
||||
tooltip={t`Add parameter`}
|
||||
onClick={() => newParameter.open()}
|
||||
/>
|
||||
];
|
||||
}, [partLocked, user]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{newParameter.modal}
|
||||
{editParameter.modal}
|
||||
{deleteParameter.modal}
|
||||
<Stack gap='xs'>
|
||||
{partLocked && (
|
||||
<Alert
|
||||
title={t`Part is Locked`}
|
||||
color='orange'
|
||||
icon={<IconLock />}
|
||||
p='xs'
|
||||
>
|
||||
<Text>{t`Part parameters cannot be edited, as the part is locked`}</Text>
|
||||
</Alert>
|
||||
)}
|
||||
<InvenTreeTable
|
||||
url={apiUrl(ApiEndpoints.part_parameter_list)}
|
||||
tableState={table}
|
||||
columns={tableColumns}
|
||||
props={{
|
||||
rowActions: rowActions,
|
||||
enableDownload: true,
|
||||
tableActions: tableActions,
|
||||
tableFilters: tableFilters,
|
||||
params: {
|
||||
part: partId,
|
||||
template_detail: true,
|
||||
part_detail: true
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user