2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-06-16 03:55:41 +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:
Oliver
2023-10-18 00:41:05 +11:00
committed by GitHub
parent 997b2ad569
commit 8c10b98fe8
10 changed files with 199 additions and 121 deletions

View File

@ -11,8 +11,9 @@ import {
Overlay,
Stack
} from '@mantine/core';
import { useId } from '@mantine/hooks';
import { useQuery } from '@tanstack/react-query';
import { useEffect, useState } from 'react';
import { useState } from 'react';
import { api } from '../../App';
@ -22,8 +23,10 @@ import { api } from '../../App';
export function ApiImage(props: ImageProps) {
const [image, setImage] = useState<string>('');
const queryKey = useId();
const imgQuery = useQuery({
queryKey: ['image', props.src],
queryKey: ['image', queryKey, props.src],
enabled: props.src != undefined && props.src != null && props.src != '',
queryFn: async () => {
if (!props.src) {
@ -47,7 +50,7 @@ export function ApiImage(props: ImageProps) {
});
},
refetchOnMount: true,
refetchOnWindowFocus: true
refetchOnWindowFocus: false
});
return (

View File

@ -11,7 +11,7 @@ export function Thumbnail({
alt = t`Thumbnail`,
size = 20
}: {
src: string;
src?: string | undefined;
alt?: string;
size?: number;
}) {

View File

@ -1,5 +1,5 @@
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 { useCallback, useMemo } from 'react';
@ -10,6 +10,7 @@ import {
} from '../../../functions/forms';
import { useTableRefresh } from '../../../hooks/TableRefresh';
import { ApiPaths, apiUrl } from '../../../states/ApiState';
import { Thumbnail } from '../../images/Thumbnail';
import { YesNoButton } from '../../items/YesNoButton';
import { TableColumn } from '../Column';
import { InvenTreeTable } from '../InvenTreeTable';
@ -22,12 +23,36 @@ export function PartParameterTable({ partId }: { partId: any }) {
const tableColumns: TableColumn[] = useMemo(() => {
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',
title: t`Parameter`,
switchable: false,
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',
@ -65,54 +90,62 @@ export function PartParameterTable({ partId }: { partId: any }) {
render: (record) => record.template_detail?.units
}
];
}, []);
}, [partId]);
// Callback for row actions
// TODO: Adjust based on user permissions
const rowActions = useCallback((record: any) => {
let actions = [];
const rowActions = useCallback(
(record: any) => {
// Actions not allowed for "variant" rows
if (String(partId) != String(record.part)) {
return [];
}
actions.push({
title: t`Edit`,
onClick: () => {
openEditApiForm({
name: 'edit-part-parameter',
url: ApiPaths.part_parameter_list,
pk: record.pk,
title: t`Edit Part Parameter`,
fields: {
part: {
hidden: true
let actions = [];
actions.push({
title: t`Edit`,
onClick: () => {
openEditApiForm({
name: 'edit-part-parameter',
url: ApiPaths.part_parameter_list,
pk: record.pk,
title: t`Edit Part Parameter`,
fields: {
part: {
hidden: true
},
template: {},
data: {}
},
template: {},
data: {}
},
successMessage: t`Part parameter updated`,
onFormSuccess: refreshTable
});
}
});
successMessage: t`Part parameter updated`,
onFormSuccess: refreshTable
});
}
});
actions.push({
title: t`Delete`,
color: 'red',
onClick: () => {
openDeleteApiForm({
name: 'delete-part-parameter',
url: ApiPaths.part_parameter_list,
pk: record.pk,
title: t`Delete Part Parameter`,
successMessage: t`Part parameter deleted`,
onFormSuccess: refreshTable,
preFormContent: (
<Text>{t`Are you sure you want to remove this parameter?`}</Text>
)
});
}
});
actions.push({
title: t`Delete`,
color: 'red',
onClick: () => {
openDeleteApiForm({
name: 'delete-part-parameter',
url: ApiPaths.part_parameter_list,
pk: record.pk,
title: t`Delete Part Parameter`,
successMessage: t`Part parameter deleted`,
onFormSuccess: refreshTable,
preFormContent: (
<Text>{t`Are you sure you want to remove this parameter?`}</Text>
)
});
}
});
return actions;
}, []);
return actions;
},
[partId]
);
const addParameter = useCallback(() => {
if (!partId) {
@ -160,9 +193,17 @@ export function PartParameterTable({ partId }: { partId: any }) {
props={{
rowActions: rowActions,
customActionGroups: tableActions,
customFilters: [
{
name: 'include_variants',
label: t`Include Variants`,
type: 'boolean'
}
],
params: {
part: partId,
template_detail: true
template_detail: true,
part_detail: true
}
}}
/>

View File

@ -3,8 +3,6 @@ import { Group, Text } from '@mantine/core';
import { useMemo } from 'react';
import { useNavigate } from 'react-router-dom';
import { editPart } from '../../../functions/forms/PartForms';
import { notYetImplemented } from '../../../functions/notifications';
import { shortenString } from '../../../functions/tables';
import { useTableRefresh } from '../../../hooks/TableRefresh';
import { ApiPaths, apiUrl } from '../../../states/ApiState';
@ -12,7 +10,6 @@ import { Thumbnail } from '../../images/Thumbnail';
import { TableColumn } from '../Column';
import { TableFilter } from '../Filter';
import { InvenTreeTable, InvenTreeTableProps } from '../InvenTreeTable';
import { RowAction } from '../RowActions';
/**
* Construct a list of columns for the part table
@ -193,33 +190,6 @@ export function PartListTable({ props }: { props: InvenTreeTableProps }) {
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();
return (
@ -231,10 +201,12 @@ export function PartListTable({ props }: { props: InvenTreeTableProps }) {
...props,
enableDownload: true,
customFilters: tableFilters,
rowActions: partTableRowActions,
params: {
...props.params,
category_detail: true
},
onRowClick: (record, _index, _event) => {
navigate(`/part/${record.pk}/`);
}
}}
/>

View 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
}
}}
/>
);
}

View File

@ -30,7 +30,7 @@ function stockItemTableColumns(): TableColumn[] {
alt={part?.name}
size={24}
/>
<Text>{part.full_name}</Text>
<Text>{part?.full_name}</Text>
</Group>
);
}

View File

@ -7,10 +7,11 @@ export function shortenString({
str,
len = 100
}: {
str: string;
str: string | undefined;
len?: number;
}) {
// Ensure that the string is a string
str = str ?? '';
str = str.toString();
// If the string is already short enough, return it

View File

@ -33,6 +33,7 @@ import { PageDetail } from '../../components/nav/PageDetail';
import { PanelGroup, PanelType } from '../../components/nav/PanelGroup';
import { AttachmentTable } from '../../components/tables/AttachmentTable';
import { PartParameterTable } from '../../components/tables/part/PartParameterTable';
import { PartVariantTable } from '../../components/tables/part/PartVariantTable';
import { RelatedPartTable } from '../../components/tables/part/RelatedPartTable';
import { StockItemTable } from '../../components/tables/stock/StockItemTable';
import { NotesEditor } from '../../components/widgets/MarkdownEditor';
@ -56,8 +57,7 @@ export default function PartDetail() {
params: {
path_detail: true
},
refetchOnMount: true,
refetchOnWindowFocus: true
refetchOnMount: true
});
// Part data panels (recalculate when part data changes)
@ -92,7 +92,7 @@ export default function PartDetail() {
label: t`Variants`,
icon: <IconVersions />,
hidden: !part.is_template,
content: <PlaceholderPanel />
content: <PartVariantTable partId={String(id)} />
},
{
name: 'bom',