mirror of
https://github.com/inventree/InvenTree.git
synced 2025-12-16 17:28:11 +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 generic.states.api import urlpattern as generic_states_api_urls
|
||||||
from InvenTree.api import BulkDeleteMixin, MetadataView
|
from InvenTree.api import BulkDeleteMixin, MetadataView
|
||||||
from InvenTree.config import CONFIG_LOOKUPS
|
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 import inheritors, str2bool
|
||||||
from InvenTree.helpers_email import send_email
|
from InvenTree.helpers_email import send_email
|
||||||
from InvenTree.mixins import (
|
from InvenTree.mixins import (
|
||||||
@@ -837,6 +841,10 @@ class ParameterFilter(FilterSet):
|
|||||||
model = common.models.Parameter
|
model = common.models.Parameter
|
||||||
fields = ['model_type', 'model_id', 'template', 'updated_by']
|
fields = ['model_type', 'model_id', 'template', 'updated_by']
|
||||||
|
|
||||||
|
enabled = rest_filters.BooleanFilter(
|
||||||
|
label='Template Enabled', field_name='template__enabled'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ParameterMixin:
|
class ParameterMixin:
|
||||||
"""Mixin class for Parameter views."""
|
"""Mixin class for Parameter views."""
|
||||||
@@ -856,7 +864,7 @@ class ParameterList(
|
|||||||
"""List API endpoint for Parameter objects."""
|
"""List API endpoint for Parameter objects."""
|
||||||
|
|
||||||
filterset_class = ParameterFilter
|
filterset_class = ParameterFilter
|
||||||
filter_backends = SEARCH_ORDER_FILTER
|
filter_backends = SEARCH_ORDER_FILTER_ALIAS
|
||||||
|
|
||||||
ordering_fields = ['name', 'data', 'units', 'template', 'updated', 'updated_by']
|
ordering_fields = ['name', 'data', 'units', 'template', 'updated', 'updated_by']
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,11 @@ export const ModelInformationDict: ModelDict = {
|
|||||||
admin_url: '/part/part/',
|
admin_url: '/part/part/',
|
||||||
icon: 'part'
|
icon: 'part'
|
||||||
},
|
},
|
||||||
|
parametertemplate: {
|
||||||
|
label: () => t`Parameter Template`,
|
||||||
|
label_multiple: () => t`Parameter Templates`,
|
||||||
|
api_endpoint: ApiEndpoints.parameter_template_list
|
||||||
|
},
|
||||||
partparametertemplate: {
|
partparametertemplate: {
|
||||||
label: () => t`Part Parameter Template`,
|
label: () => t`Part Parameter Template`,
|
||||||
label_multiple: () => t`Part Parameter Templates`,
|
label_multiple: () => t`Part Parameter Templates`,
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ export enum ModelType {
|
|||||||
buildline = 'buildline',
|
buildline = 'buildline',
|
||||||
builditem = 'builditem',
|
builditem = 'builditem',
|
||||||
company = 'company',
|
company = 'company',
|
||||||
|
parametertemplate = 'parametertemplate',
|
||||||
purchaseorder = 'purchaseorder',
|
purchaseorder = 'purchaseorder',
|
||||||
purchaseorderlineitem = 'purchaseorderlineitem',
|
purchaseorderlineitem = 'purchaseorderlineitem',
|
||||||
salesorder = 'salesorder',
|
salesorder = 'salesorder',
|
||||||
|
|||||||
@@ -2,6 +2,18 @@ import type { ReactNode } from 'react';
|
|||||||
|
|
||||||
import { type InstanceRenderInterface, RenderInlineModel } from './Instance';
|
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({
|
export function RenderProjectCode({
|
||||||
instance
|
instance
|
||||||
}: Readonly<InstanceRenderInterface>): ReactNode {
|
}: Readonly<InstanceRenderInterface>): ReactNode {
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import {
|
|||||||
RenderContentType,
|
RenderContentType,
|
||||||
RenderError,
|
RenderError,
|
||||||
RenderImportSession,
|
RenderImportSession,
|
||||||
|
RenderParameterTemplate,
|
||||||
RenderProjectCode,
|
RenderProjectCode,
|
||||||
RenderSelectionList
|
RenderSelectionList
|
||||||
} from './Generic';
|
} from './Generic';
|
||||||
@@ -71,6 +72,7 @@ export const RendererLookup: ModelRendererDict = {
|
|||||||
[ModelType.builditem]: RenderBuildItem,
|
[ModelType.builditem]: RenderBuildItem,
|
||||||
[ModelType.company]: RenderCompany,
|
[ModelType.company]: RenderCompany,
|
||||||
[ModelType.contact]: RenderContact,
|
[ModelType.contact]: RenderContact,
|
||||||
|
[ModelType.parametertemplate]: RenderParameterTemplate,
|
||||||
[ModelType.manufacturerpart]: RenderManufacturerPart,
|
[ModelType.manufacturerpart]: RenderManufacturerPart,
|
||||||
[ModelType.owner]: RenderOwner,
|
[ModelType.owner]: RenderOwner,
|
||||||
[ModelType.part]: RenderPart,
|
[ModelType.part]: RenderPart,
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
import { IconUsers } from '@tabler/icons-react';
|
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 type { ApiFormFieldSet } from '@lib/types/Forms';
|
||||||
import { t } from '@lingui/core/macro';
|
import { t } from '@lingui/core/macro';
|
||||||
import type {
|
import type {
|
||||||
StatusCodeInterface,
|
StatusCodeInterface,
|
||||||
StatusCodeListInterface
|
StatusCodeListInterface
|
||||||
} from '../components/render/StatusRenderer';
|
} from '../components/render/StatusRenderer';
|
||||||
|
import { useApi } from '../contexts/ApiContext';
|
||||||
import { useGlobalStatusState } from '../states/GlobalStatusState';
|
import { useGlobalStatusState } from '../states/GlobalStatusState';
|
||||||
|
|
||||||
export function projectCodeFields(): ApiFormFieldSet {
|
export function projectCodeFields(): ApiFormFieldSet {
|
||||||
@@ -91,3 +95,117 @@ export function extraLineItemFields(): ApiFormFieldSet {
|
|||||||
link: {}
|
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 { BomTable } from '../../tables/bom/BomTable';
|
||||||
import { UsedInTable } from '../../tables/bom/UsedInTable';
|
import { UsedInTable } from '../../tables/bom/UsedInTable';
|
||||||
import { BuildOrderTable } from '../../tables/build/BuildOrderTable';
|
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 PartPurchaseOrdersTable from '../../tables/part/PartPurchaseOrdersTable';
|
||||||
import PartTestResultTable from '../../tables/part/PartTestResultTable';
|
import PartTestResultTable from '../../tables/part/PartTestResultTable';
|
||||||
import PartTestTemplateTable from '../../tables/part/PartTestTemplateTable';
|
import PartTestTemplateTable from '../../tables/part/PartTestTemplateTable';
|
||||||
@@ -792,11 +792,10 @@ export default function PartDetail() {
|
|||||||
name: 'parameters',
|
name: 'parameters',
|
||||||
label: t`Parameters`,
|
label: t`Parameters`,
|
||||||
icon: <IconList />,
|
icon: <IconList />,
|
||||||
content: (
|
content: part?.pk ? (
|
||||||
<PartParameterTable
|
<ParameterTable modelType={ModelType.part} modelId={part?.pk} />
|
||||||
partId={id ?? -1}
|
) : (
|
||||||
partLocked={part?.locked == true}
|
<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 { TableFilter } from '@lib/types/Filters';
|
||||||
import type { TableColumn } from '@lib/types/Tables';
|
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 { useTable } from '../../hooks/UseTable';
|
||||||
import { useUserState } from '../../states/UserState';
|
import { useUserState } from '../../states/UserState';
|
||||||
|
import {
|
||||||
|
DateColumn,
|
||||||
|
DescriptionColumn,
|
||||||
|
NoteColumn,
|
||||||
|
UserColumn
|
||||||
|
} from '../ColumnRenderers';
|
||||||
|
import { UserFilter } from '../Filter';
|
||||||
import { InvenTreeTable } from '../InvenTreeTable';
|
import { InvenTreeTable } from '../InvenTreeTable';
|
||||||
|
import { TableHoverCard } from '../TableHoverCard';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a table listing parameters
|
* Construct a table listing parameters
|
||||||
@@ -20,32 +44,159 @@ export function ParameterTable({
|
|||||||
const user = useUserState();
|
const user = useUserState();
|
||||||
|
|
||||||
const tableColumns: TableColumn[] = useMemo(() => {
|
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]);
|
}, [user]);
|
||||||
|
|
||||||
const tableFilters: TableFilter[] = useMemo(() => {
|
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(() => {
|
const tableActions = useMemo(() => {
|
||||||
// TODO
|
return [
|
||||||
return [];
|
<AddItemButton
|
||||||
|
key='add-parameter'
|
||||||
|
hidden={!user.hasAddPermission(modelType)}
|
||||||
|
onClick={() => {
|
||||||
|
setSelectedParameter(undefined);
|
||||||
|
newParameter.open();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
];
|
||||||
}, [user]);
|
}, [user]);
|
||||||
|
|
||||||
const rowActions = useCallback(() => {
|
const rowActions = useCallback(
|
||||||
return [];
|
(record: any) => {
|
||||||
}, [user]);
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
|
{newParameter.modal}
|
||||||
|
{editParameter.modal}
|
||||||
|
{deleteParameter.modal}
|
||||||
<InvenTreeTable
|
<InvenTreeTable
|
||||||
url={apiUrl(ApiEndpoints.parameter_list)}
|
url={apiUrl(ApiEndpoints.parameter_list)}
|
||||||
tableState={table}
|
tableState={table}
|
||||||
columns={tableColumns}
|
columns={tableColumns}
|
||||||
props={{
|
props={{
|
||||||
enableDownload: true,
|
enableDownload: true,
|
||||||
|
enableBulkDelete: true,
|
||||||
|
enableSelection: true,
|
||||||
rowActions: rowActions,
|
rowActions: rowActions,
|
||||||
tableActions: tableActions,
|
tableActions: tableActions,
|
||||||
tableFilters: tableFilters,
|
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