mirror of
https://github.com/inventree/InvenTree.git
synced 2025-04-30 04:26:44 +00:00
[React] Update part parameters table (#5731)
* Implement simple "PartVariantTable" component - Not yet nested - More work needed for table nesting * Fix issue rendering same image multiple times - Use useId hook to generate random key * Update PartParameter list API endpoint - Allow part_detail extra field - Add FilterSet class - Allow filter to include variants * Update PartParameterTable - Display part column - Allow returned parts to include templates of base part - Hide actions for templated parameters * Fix some code smells
This commit is contained in:
parent
997b2ad569
commit
8c10b98fe8
@ -1475,14 +1475,22 @@ class PartParameterAPIMixin:
|
|||||||
queryset = PartParameter.objects.all()
|
queryset = PartParameter.objects.all()
|
||||||
serializer_class = part_serializers.PartParameterSerializer
|
serializer_class = part_serializers.PartParameterSerializer
|
||||||
|
|
||||||
|
def get_queryset(self, *args, **kwargs):
|
||||||
|
"""Override get_queryset method to prefetch related fields"""
|
||||||
|
queryset = super().get_queryset(*args, **kwargs)
|
||||||
|
queryset = queryset.prefetch_related('part', 'template')
|
||||||
|
return queryset
|
||||||
|
|
||||||
def get_serializer(self, *args, **kwargs):
|
def get_serializer(self, *args, **kwargs):
|
||||||
"""Return the serializer instance for this API endpoint.
|
"""Return the serializer instance for this API endpoint.
|
||||||
|
|
||||||
If requested, extra detail fields are annotated to the queryset:
|
If requested, extra detail fields are annotated to the queryset:
|
||||||
|
- part_detail
|
||||||
- template_detail
|
- template_detail
|
||||||
"""
|
"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
kwargs['part_detail'] = str2bool(self.request.GET.get('part_detail', False))
|
||||||
kwargs['template_detail'] = str2bool(self.request.GET.get('template_detail', True))
|
kwargs['template_detail'] = str2bool(self.request.GET.get('template_detail', True))
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
@ -1490,6 +1498,35 @@ class PartParameterAPIMixin:
|
|||||||
return self.serializer_class(*args, **kwargs)
|
return self.serializer_class(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class PartParameterFilter(rest_filters.FilterSet):
|
||||||
|
"""Custom filters for the PartParameterList API endpoint"""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Metaclass options for the filterset"""
|
||||||
|
model = PartParameter
|
||||||
|
fields = [
|
||||||
|
'template'
|
||||||
|
]
|
||||||
|
|
||||||
|
part = rest_filters.ModelChoiceFilter(queryset=Part.objects.all(), method='filter_part')
|
||||||
|
|
||||||
|
def filter_part(self, queryset, name, part):
|
||||||
|
"""Filter against the provided part.
|
||||||
|
|
||||||
|
If 'include_variants' query parameter is provided, filter against variant parts also
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
include_variants = str2bool(self.request.GET.get('include_variants', False))
|
||||||
|
except AttributeError:
|
||||||
|
include_variants = False
|
||||||
|
|
||||||
|
if include_variants:
|
||||||
|
return queryset.filter(part__in=part.get_descendants(include_self=True))
|
||||||
|
else:
|
||||||
|
return queryset.filter(part=part)
|
||||||
|
|
||||||
|
|
||||||
class PartParameterList(PartParameterAPIMixin, ListCreateAPI):
|
class PartParameterList(PartParameterAPIMixin, ListCreateAPI):
|
||||||
"""API endpoint for accessing a list of PartParameter objects.
|
"""API endpoint for accessing a list of PartParameter objects.
|
||||||
|
|
||||||
@ -1497,11 +1534,15 @@ class PartParameterList(PartParameterAPIMixin, ListCreateAPI):
|
|||||||
- POST: Create a new PartParameter object
|
- POST: Create a new PartParameter object
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
filterset_class = PartParameterFilter
|
||||||
|
|
||||||
filter_backends = SEARCH_ORDER_FILTER_ALIAS
|
filter_backends = SEARCH_ORDER_FILTER_ALIAS
|
||||||
|
|
||||||
ordering_fields = [
|
ordering_fields = [
|
||||||
'name',
|
'name',
|
||||||
'data',
|
'data',
|
||||||
|
'part',
|
||||||
|
'template',
|
||||||
]
|
]
|
||||||
|
|
||||||
ordering_field_aliases = {
|
ordering_field_aliases = {
|
||||||
@ -1516,11 +1557,6 @@ class PartParameterList(PartParameterAPIMixin, ListCreateAPI):
|
|||||||
'template__units',
|
'template__units',
|
||||||
]
|
]
|
||||||
|
|
||||||
filterset_fields = [
|
|
||||||
'part',
|
|
||||||
'template',
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class PartParameterDetail(PartParameterAPIMixin, RetrieveUpdateDestroyAPI):
|
class PartParameterDetail(PartParameterAPIMixin, RetrieveUpdateDestroyAPI):
|
||||||
"""API endpoint for detail view of a single PartParameter object."""
|
"""API endpoint for detail view of a single PartParameter object."""
|
||||||
|
@ -240,37 +240,6 @@ class PartParameterTemplateSerializer(InvenTree.serializers.InvenTreeModelSerial
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class PartParameterSerializer(InvenTree.serializers.InvenTreeModelSerializer):
|
|
||||||
"""JSON serializers for the PartParameter model."""
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
"""Metaclass defining serializer fields"""
|
|
||||||
model = PartParameter
|
|
||||||
fields = [
|
|
||||||
'pk',
|
|
||||||
'part',
|
|
||||||
'template',
|
|
||||||
'template_detail',
|
|
||||||
'data',
|
|
||||||
'data_numeric',
|
|
||||||
]
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
"""Custom initialization method for the serializer.
|
|
||||||
|
|
||||||
Allows us to optionally include or exclude particular information
|
|
||||||
"""
|
|
||||||
|
|
||||||
template_detail = kwargs.pop('template_detail', True)
|
|
||||||
|
|
||||||
super().__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
if not template_detail:
|
|
||||||
self.fields.pop('template_detail')
|
|
||||||
|
|
||||||
template_detail = PartParameterTemplateSerializer(source='template', many=False, read_only=True)
|
|
||||||
|
|
||||||
|
|
||||||
class PartBriefSerializer(InvenTree.serializers.InvenTreeModelSerializer):
|
class PartBriefSerializer(InvenTree.serializers.InvenTreeModelSerializer):
|
||||||
"""Serializer for Part (brief detail)"""
|
"""Serializer for Part (brief detail)"""
|
||||||
|
|
||||||
@ -321,6 +290,43 @@ class PartBriefSerializer(InvenTree.serializers.InvenTreeModelSerializer):
|
|||||||
pricing_max = InvenTree.serializers.InvenTreeMoneySerializer(source='pricing_data.overall_max', allow_null=True, read_only=True)
|
pricing_max = InvenTree.serializers.InvenTreeMoneySerializer(source='pricing_data.overall_max', allow_null=True, read_only=True)
|
||||||
|
|
||||||
|
|
||||||
|
class PartParameterSerializer(InvenTree.serializers.InvenTreeModelSerializer):
|
||||||
|
"""JSON serializers for the PartParameter model."""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
"""Metaclass defining serializer fields"""
|
||||||
|
model = PartParameter
|
||||||
|
fields = [
|
||||||
|
'pk',
|
||||||
|
'part',
|
||||||
|
'part_detail',
|
||||||
|
'template',
|
||||||
|
'template_detail',
|
||||||
|
'data',
|
||||||
|
'data_numeric',
|
||||||
|
]
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
"""Custom initialization method for the serializer.
|
||||||
|
|
||||||
|
Allows us to optionally include or exclude particular information
|
||||||
|
"""
|
||||||
|
|
||||||
|
template_detail = kwargs.pop('template_detail', True)
|
||||||
|
part_detail = kwargs.pop('part_detail', False)
|
||||||
|
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
if not part_detail:
|
||||||
|
self.fields.pop('part_detail')
|
||||||
|
|
||||||
|
if not template_detail:
|
||||||
|
self.fields.pop('template_detail')
|
||||||
|
|
||||||
|
part_detail = PartBriefSerializer(source='part', many=False, read_only=True)
|
||||||
|
template_detail = PartParameterTemplateSerializer(source='template', many=False, read_only=True)
|
||||||
|
|
||||||
|
|
||||||
class PartSetCategorySerializer(serializers.Serializer):
|
class PartSetCategorySerializer(serializers.Serializer):
|
||||||
"""Serializer for changing PartCategory for multiple Part objects"""
|
"""Serializer for changing PartCategory for multiple Part objects"""
|
||||||
|
|
||||||
@ -1472,7 +1478,8 @@ class BomImportExtractSerializer(InvenTree.serializers.DataFileExtractSerializer
|
|||||||
# At least one part column is required!
|
# At least one part column is required!
|
||||||
raise serializers.ValidationError(_("No part column specified"))
|
raise serializers.ValidationError(_("No part column specified"))
|
||||||
|
|
||||||
def process_row(self, row):
|
@staticmethod
|
||||||
|
def process_row(row):
|
||||||
"""Process a single row from the loaded BOM file"""
|
"""Process a single row from the loaded BOM file"""
|
||||||
# Skip any rows which are at a lower "level"
|
# Skip any rows which are at a lower "level"
|
||||||
level = row.get('level', None)
|
level = row.get('level', None)
|
||||||
|
@ -11,8 +11,9 @@ import {
|
|||||||
Overlay,
|
Overlay,
|
||||||
Stack
|
Stack
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
|
import { useId } from '@mantine/hooks';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { useEffect, useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
import { api } from '../../App';
|
import { api } from '../../App';
|
||||||
|
|
||||||
@ -22,8 +23,10 @@ import { api } from '../../App';
|
|||||||
export function ApiImage(props: ImageProps) {
|
export function ApiImage(props: ImageProps) {
|
||||||
const [image, setImage] = useState<string>('');
|
const [image, setImage] = useState<string>('');
|
||||||
|
|
||||||
|
const queryKey = useId();
|
||||||
|
|
||||||
const imgQuery = useQuery({
|
const imgQuery = useQuery({
|
||||||
queryKey: ['image', props.src],
|
queryKey: ['image', queryKey, props.src],
|
||||||
enabled: props.src != undefined && props.src != null && props.src != '',
|
enabled: props.src != undefined && props.src != null && props.src != '',
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
if (!props.src) {
|
if (!props.src) {
|
||||||
@ -47,7 +50,7 @@ export function ApiImage(props: ImageProps) {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
refetchOnMount: true,
|
refetchOnMount: true,
|
||||||
refetchOnWindowFocus: true
|
refetchOnWindowFocus: false
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -11,7 +11,7 @@ export function Thumbnail({
|
|||||||
alt = t`Thumbnail`,
|
alt = t`Thumbnail`,
|
||||||
size = 20
|
size = 20
|
||||||
}: {
|
}: {
|
||||||
src: string;
|
src?: string | undefined;
|
||||||
alt?: string;
|
alt?: string;
|
||||||
size?: number;
|
size?: number;
|
||||||
}) {
|
}) {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { ActionIcon, Text, Tooltip } from '@mantine/core';
|
import { ActionIcon, Group, Text, Tooltip } from '@mantine/core';
|
||||||
import { IconTextPlus } from '@tabler/icons-react';
|
import { IconTextPlus } from '@tabler/icons-react';
|
||||||
import { useCallback, useMemo } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
|
|
||||||
@ -10,6 +10,7 @@ import {
|
|||||||
} from '../../../functions/forms';
|
} from '../../../functions/forms';
|
||||||
import { useTableRefresh } from '../../../hooks/TableRefresh';
|
import { useTableRefresh } from '../../../hooks/TableRefresh';
|
||||||
import { ApiPaths, apiUrl } from '../../../states/ApiState';
|
import { ApiPaths, apiUrl } from '../../../states/ApiState';
|
||||||
|
import { Thumbnail } from '../../images/Thumbnail';
|
||||||
import { YesNoButton } from '../../items/YesNoButton';
|
import { YesNoButton } from '../../items/YesNoButton';
|
||||||
import { TableColumn } from '../Column';
|
import { TableColumn } from '../Column';
|
||||||
import { InvenTreeTable } from '../InvenTreeTable';
|
import { InvenTreeTable } from '../InvenTreeTable';
|
||||||
@ -22,12 +23,36 @@ export function PartParameterTable({ partId }: { partId: any }) {
|
|||||||
|
|
||||||
const tableColumns: TableColumn[] = useMemo(() => {
|
const tableColumns: TableColumn[] = useMemo(() => {
|
||||||
return [
|
return [
|
||||||
|
{
|
||||||
|
accessor: 'part',
|
||||||
|
title: t`Part`,
|
||||||
|
switchable: true,
|
||||||
|
sortable: true,
|
||||||
|
render: function (record: any) {
|
||||||
|
let part = record?.part_detail ?? {};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Group spacing="xs" align="left" noWrap={true}>
|
||||||
|
<Thumbnail
|
||||||
|
src={part?.thumbnail || part?.image}
|
||||||
|
alt={part?.name}
|
||||||
|
size={24}
|
||||||
|
/>
|
||||||
|
<Text>{part?.full_name}</Text>
|
||||||
|
</Group>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
accessor: 'name',
|
accessor: 'name',
|
||||||
title: t`Parameter`,
|
title: t`Parameter`,
|
||||||
switchable: false,
|
switchable: false,
|
||||||
sortable: true,
|
sortable: true,
|
||||||
render: (record) => record.template_detail?.name
|
render: (record) => {
|
||||||
|
let variant = String(partId) != String(record.part);
|
||||||
|
|
||||||
|
return <Text italic={variant}>{record.template_detail?.name}</Text>;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessor: 'description',
|
accessor: 'description',
|
||||||
@ -65,11 +90,17 @@ export function PartParameterTable({ partId }: { partId: any }) {
|
|||||||
render: (record) => record.template_detail?.units
|
render: (record) => record.template_detail?.units
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
}, []);
|
}, [partId]);
|
||||||
|
|
||||||
// Callback for row actions
|
// Callback for row actions
|
||||||
// TODO: Adjust based on user permissions
|
// TODO: Adjust based on user permissions
|
||||||
const rowActions = useCallback((record: any) => {
|
const rowActions = useCallback(
|
||||||
|
(record: any) => {
|
||||||
|
// Actions not allowed for "variant" rows
|
||||||
|
if (String(partId) != String(record.part)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
let actions = [];
|
let actions = [];
|
||||||
|
|
||||||
actions.push({
|
actions.push({
|
||||||
@ -112,7 +143,9 @@ export function PartParameterTable({ partId }: { partId: any }) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return actions;
|
return actions;
|
||||||
}, []);
|
},
|
||||||
|
[partId]
|
||||||
|
);
|
||||||
|
|
||||||
const addParameter = useCallback(() => {
|
const addParameter = useCallback(() => {
|
||||||
if (!partId) {
|
if (!partId) {
|
||||||
@ -160,9 +193,17 @@ export function PartParameterTable({ partId }: { partId: any }) {
|
|||||||
props={{
|
props={{
|
||||||
rowActions: rowActions,
|
rowActions: rowActions,
|
||||||
customActionGroups: tableActions,
|
customActionGroups: tableActions,
|
||||||
|
customFilters: [
|
||||||
|
{
|
||||||
|
name: 'include_variants',
|
||||||
|
label: t`Include Variants`,
|
||||||
|
type: 'boolean'
|
||||||
|
}
|
||||||
|
],
|
||||||
params: {
|
params: {
|
||||||
part: partId,
|
part: partId,
|
||||||
template_detail: true
|
template_detail: true,
|
||||||
|
part_detail: true
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -3,8 +3,6 @@ import { Group, Text } from '@mantine/core';
|
|||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
import { editPart } from '../../../functions/forms/PartForms';
|
|
||||||
import { notYetImplemented } from '../../../functions/notifications';
|
|
||||||
import { shortenString } from '../../../functions/tables';
|
import { shortenString } from '../../../functions/tables';
|
||||||
import { useTableRefresh } from '../../../hooks/TableRefresh';
|
import { useTableRefresh } from '../../../hooks/TableRefresh';
|
||||||
import { ApiPaths, apiUrl } from '../../../states/ApiState';
|
import { ApiPaths, apiUrl } from '../../../states/ApiState';
|
||||||
@ -12,7 +10,6 @@ import { Thumbnail } from '../../images/Thumbnail';
|
|||||||
import { TableColumn } from '../Column';
|
import { TableColumn } from '../Column';
|
||||||
import { TableFilter } from '../Filter';
|
import { TableFilter } from '../Filter';
|
||||||
import { InvenTreeTable, InvenTreeTableProps } from '../InvenTreeTable';
|
import { InvenTreeTable, InvenTreeTableProps } from '../InvenTreeTable';
|
||||||
import { RowAction } from '../RowActions';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a list of columns for the part table
|
* Construct a list of columns for the part table
|
||||||
@ -193,33 +190,6 @@ export function PartListTable({ props }: { props: InvenTreeTableProps }) {
|
|||||||
|
|
||||||
const { tableKey, refreshTable } = useTableRefresh('part');
|
const { tableKey, refreshTable } = useTableRefresh('part');
|
||||||
|
|
||||||
// Callback function for generating set of row actions
|
|
||||||
function partTableRowActions(record: any): RowAction[] {
|
|
||||||
let actions: RowAction[] = [];
|
|
||||||
|
|
||||||
actions.push({
|
|
||||||
title: t`Edit`,
|
|
||||||
onClick: () => {
|
|
||||||
editPart({
|
|
||||||
part_id: record.pk,
|
|
||||||
callback: () => {
|
|
||||||
// TODO: Reload the table, somehow?
|
|
||||||
notYetImplemented();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
actions.push({
|
|
||||||
title: t`Detail`,
|
|
||||||
onClick: () => {
|
|
||||||
navigate(`/part/${record.pk}/`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return actions;
|
|
||||||
}
|
|
||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -231,10 +201,12 @@ export function PartListTable({ props }: { props: InvenTreeTableProps }) {
|
|||||||
...props,
|
...props,
|
||||||
enableDownload: true,
|
enableDownload: true,
|
||||||
customFilters: tableFilters,
|
customFilters: tableFilters,
|
||||||
rowActions: partTableRowActions,
|
|
||||||
params: {
|
params: {
|
||||||
...props.params,
|
...props.params,
|
||||||
category_detail: true
|
category_detail: true
|
||||||
|
},
|
||||||
|
onRowClick: (record, _index, _event) => {
|
||||||
|
navigate(`/part/${record.pk}/`);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
18
src/frontend/src/components/tables/part/PartVariantTable.tsx
Normal file
18
src/frontend/src/components/tables/part/PartVariantTable.tsx
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { PartListTable } from './PartTable';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display variant parts for thespecified parent part
|
||||||
|
*/
|
||||||
|
export function PartVariantTable({ partId }: { partId: string }) {
|
||||||
|
return (
|
||||||
|
<PartListTable
|
||||||
|
props={{
|
||||||
|
enableDownload: false,
|
||||||
|
customFilters: [],
|
||||||
|
params: {
|
||||||
|
ancestor: partId
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
@ -30,7 +30,7 @@ function stockItemTableColumns(): TableColumn[] {
|
|||||||
alt={part?.name}
|
alt={part?.name}
|
||||||
size={24}
|
size={24}
|
||||||
/>
|
/>
|
||||||
<Text>{part.full_name}</Text>
|
<Text>{part?.full_name}</Text>
|
||||||
</Group>
|
</Group>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -7,10 +7,11 @@ export function shortenString({
|
|||||||
str,
|
str,
|
||||||
len = 100
|
len = 100
|
||||||
}: {
|
}: {
|
||||||
str: string;
|
str: string | undefined;
|
||||||
len?: number;
|
len?: number;
|
||||||
}) {
|
}) {
|
||||||
// Ensure that the string is a string
|
// Ensure that the string is a string
|
||||||
|
str = str ?? '';
|
||||||
str = str.toString();
|
str = str.toString();
|
||||||
|
|
||||||
// If the string is already short enough, return it
|
// If the string is already short enough, return it
|
||||||
|
@ -33,6 +33,7 @@ import { PageDetail } from '../../components/nav/PageDetail';
|
|||||||
import { PanelGroup, PanelType } from '../../components/nav/PanelGroup';
|
import { PanelGroup, PanelType } from '../../components/nav/PanelGroup';
|
||||||
import { AttachmentTable } from '../../components/tables/AttachmentTable';
|
import { AttachmentTable } from '../../components/tables/AttachmentTable';
|
||||||
import { PartParameterTable } from '../../components/tables/part/PartParameterTable';
|
import { PartParameterTable } from '../../components/tables/part/PartParameterTable';
|
||||||
|
import { PartVariantTable } from '../../components/tables/part/PartVariantTable';
|
||||||
import { RelatedPartTable } from '../../components/tables/part/RelatedPartTable';
|
import { RelatedPartTable } from '../../components/tables/part/RelatedPartTable';
|
||||||
import { StockItemTable } from '../../components/tables/stock/StockItemTable';
|
import { StockItemTable } from '../../components/tables/stock/StockItemTable';
|
||||||
import { NotesEditor } from '../../components/widgets/MarkdownEditor';
|
import { NotesEditor } from '../../components/widgets/MarkdownEditor';
|
||||||
@ -56,8 +57,7 @@ export default function PartDetail() {
|
|||||||
params: {
|
params: {
|
||||||
path_detail: true
|
path_detail: true
|
||||||
},
|
},
|
||||||
refetchOnMount: true,
|
refetchOnMount: true
|
||||||
refetchOnWindowFocus: true
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Part data panels (recalculate when part data changes)
|
// Part data panels (recalculate when part data changes)
|
||||||
@ -92,7 +92,7 @@ export default function PartDetail() {
|
|||||||
label: t`Variants`,
|
label: t`Variants`,
|
||||||
icon: <IconVersions />,
|
icon: <IconVersions />,
|
||||||
hidden: !part.is_template,
|
hidden: !part.is_template,
|
||||||
content: <PlaceholderPanel />
|
content: <PartVariantTable partId={String(id)} />
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'bom',
|
name: 'bom',
|
||||||
|
Loading…
x
Reference in New Issue
Block a user