[PUI] Implement manufacturer part table (#6115)

* Fix supplier part form

- Ensure manufacturer parts get filtered by "part" instance

* Implement simple ManufacturerPart table

* Add table actions

* Fix unused imports
This commit is contained in:
Oliver 2023-12-19 15:28:27 +11:00 committed by GitHub
parent a63529a9cf
commit be1820fb94
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 160 additions and 48 deletions

View File

@ -5,6 +5,7 @@ import { t } from '@lingui/macro';
import { formatCurrency, renderDate } from '../../defaults/formatters';
import { ModelType } from '../../enums/ModelType';
import { Thumbnail } from '../images/Thumbnail';
import { ProgressBar } from '../items/ProgressBar';
import { YesNoButton } from '../items/YesNoButton';
import { TableStatusRenderer } from '../render/StatusRenderer';
@ -12,6 +13,11 @@ import { RenderOwner } from '../render/User';
import { TableColumn } from './Column';
import { ProjectCodeHoverCard } from './TableHoverCard';
// Render a Part instance within a table
export function PartColumn(part: any) {
return <Thumbnail src={part?.thumbnail ?? part.image} text={part.name} />;
}
export function BooleanColumn({
accessor,
title

View File

@ -1,5 +1,5 @@
import { t } from '@lingui/macro';
import { Group, Text } from '@mantine/core';
import { Text } from '@mantine/core';
import { useCallback, useMemo } from 'react';
import { ApiPaths } from '../../../enums/ApiEndpoints';
@ -13,9 +13,9 @@ import { useTable } from '../../../hooks/UseTable';
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';
import { TableColumn } from '../Column';
import { PartColumn } from '../ColumnRenderers';
import { InvenTreeTable } from '../InvenTreeTable';
import { RowDeleteAction, RowEditAction } from '../RowActions';
@ -34,20 +34,7 @@ export function PartParameterTable({ partId }: { partId: any }) {
title: t`Part`,
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>
);
}
render: (record: any) => PartColumn(record?.part_detail)
},
{
accessor: 'name',

View File

@ -14,6 +14,7 @@ import { apiUrl } from '../../../states/ApiState';
import { useUserState } from '../../../states/UserState';
import { AddItemButton } from '../../buttons/AddItemButton';
import { TableColumn } from '../Column';
import { DescriptionColumn } from '../ColumnRenderers';
import { TableFilter } from '../Filter';
import { InvenTreeTable } from '../InvenTreeTable';
import { RowDeleteAction, RowEditAction } from '../RowActions';
@ -56,11 +57,7 @@ export default function PartParameterTemplateTable() {
title: t`Units`,
sortable: true
},
{
accessor: 'description',
title: t`Description`,
sortable: false
},
DescriptionColumn(),
{
accessor: 'checkbox',
title: t`Checkbox`

View File

@ -0,0 +1,119 @@
import { t } from '@lingui/macro';
import { ReactNode, useCallback, useMemo } from 'react';
import { ApiPaths } from '../../../enums/ApiEndpoints';
import { UserRoles } from '../../../enums/Roles';
import { useManufacturerPartFields } from '../../../forms/CompanyForms';
import { openDeleteApiForm, openEditApiForm } from '../../../functions/forms';
import { useTable } from '../../../hooks/UseTable';
import { apiUrl } from '../../../states/ApiState';
import { useUserState } from '../../../states/UserState';
import { Thumbnail } from '../../images/Thumbnail';
import { TableColumn } from '../Column';
import { DescriptionColumn, LinkColumn, PartColumn } from '../ColumnRenderers';
import { InvenTreeTable } from '../InvenTreeTable';
import { RowDeleteAction, RowEditAction } from '../RowActions';
/*
* Construct a table listing manufacturer parts
*/
export function ManufacturerPartTable({ params }: { params: any }): ReactNode {
const table = useTable('manufacturerparts');
const user = useUserState();
// Construct table columns for this table
const tableColumns: TableColumn[] = useMemo(() => {
return [
{
accessor: 'part',
title: t`Part`,
switchable: 'part' in params,
sortable: true,
render: (record: any) => PartColumn(record?.part_detail)
},
{
accessor: 'manufacturer',
title: t`Manufacturer`,
sortable: true,
render: (record: any) => {
let manufacturer = record?.manufacturer_detail ?? {};
return (
<Thumbnail
src={manufacturer?.thumbnail ?? manufacturer.image}
text={manufacturer.name}
/>
);
}
},
{
accessor: 'MPN',
title: t`Manufacturer Part Number`,
sortable: true
},
DescriptionColumn(),
LinkColumn()
];
}, [params]);
const tableActions = useMemo(() => {
// TODO: Custom actions
return [];
}, [user]);
const editManufacturerPartFields = useManufacturerPartFields();
const rowActions = useCallback(
(record: any) => {
return [
RowEditAction({
hidden: !user.hasChangeRole(UserRoles.purchase_order),
onClick: () => {
record.pk &&
openEditApiForm({
url: ApiPaths.manufacturer_part_list,
pk: record.pk,
title: t`Edit Manufacturer Part`,
fields: editManufacturerPartFields,
onFormSuccess: table.refreshTable,
successMessage: t`Manufacturer part updated`
});
}
}),
RowDeleteAction({
hidden: !user.hasDeleteRole(UserRoles.purchase_order),
onClick: () => {
record.pk &&
openDeleteApiForm({
url: ApiPaths.manufacturer_part_list,
pk: record.pk,
title: t`Delete Manufacturer Part`,
successMessage: t`Manufacturer part deleted`,
onFormSuccess: table.refreshTable,
preFormWarning: t`Are you sure you want to remove this manufacturer part?`
});
}
})
];
},
[user]
);
return (
<InvenTreeTable
url={apiUrl(ApiPaths.manufacturer_part_list)}
tableState={table}
columns={tableColumns}
props={{
params: {
...params,
part_detail: true,
manufacturer_detail: true
},
rowActions: rowActions,
customActionGroups: tableActions
}}
/>
);
}

View File

@ -13,7 +13,7 @@ import { useUserState } from '../../../states/UserState';
import { AddItemButton } from '../../buttons/AddItemButton';
import { Thumbnail } from '../../images/Thumbnail';
import { TableColumn } from '../Column';
import { DescriptionColumn, LinkColumn } from '../ColumnRenderers';
import { DescriptionColumn, LinkColumn, PartColumn } from '../ColumnRenderers';
import { InvenTreeTable } from '../InvenTreeTable';
import { RowDeleteAction, RowEditAction } from '../RowActions';
import { TableHoverCard } from '../TableHoverCard';
@ -35,13 +35,7 @@ export function SupplierPartTable({ params }: { params: any }): ReactNode {
title: t`Part`,
switchable: 'part' in params,
sortable: true,
render: (record: any) => {
let part = record?.part_detail ?? {};
return (
<Thumbnail src={part?.thumbnail ?? part.image} text={part.name} />
);
}
render: (record: any) => PartColumn(record?.part_detail)
},
{
accessor: 'supplier',
@ -179,7 +173,8 @@ export function SupplierPartTable({ params }: { params: any }): ReactNode {
}, [user]);
const editSupplierPartFields = useSupplierPartFields({
hidePart: true
hidePart: true,
partPk: params?.part
});
// Row action callback

View File

@ -8,9 +8,8 @@ import { ApiPaths } from '../../../enums/ApiEndpoints';
import { ModelType } from '../../../enums/ModelType';
import { useTable } from '../../../hooks/UseTable';
import { apiUrl } from '../../../states/ApiState';
import { Thumbnail } from '../../images/Thumbnail';
import { TableColumn } from '../Column';
import { StatusColumn } from '../ColumnRenderers';
import { PartColumn, StatusColumn } from '../ColumnRenderers';
import { StatusFilterOptions, TableFilter } from '../Filter';
import { TableHoverCard } from '../TableHoverCard';
import { InvenTreeTable } from './../InvenTreeTable';
@ -24,24 +23,11 @@ function stockItemTableColumns(): TableColumn[] {
accessor: 'part',
sortable: true,
title: t`Part`,
render: function (record: any) {
let part = record.part_detail ?? {};
return (
<Group spacing="xs" noWrap={true}>
<Thumbnail
src={part?.thumbnail || part?.image}
alt={part?.name}
size={24}
/>
<Text>{part?.full_name}</Text>
</Group>
);
}
render: (record: any) => PartColumn(record?.part_detail)
},
{
accessor: 'part_detail.description',
sortable: false,
title: t`Description`
},
{

View File

@ -81,6 +81,20 @@ export function useSupplierPartFields({
}, [part]);
}
export function useManufacturerPartFields() {
return useMemo(() => {
const fields: ApiFormFieldSet = {
part: {},
manufacturer: {},
MPN: {},
description: {},
link: {}
};
return fields;
}, []);
}
/**
* Field set for editing a company instance
*/

View File

@ -46,6 +46,7 @@ import { AttachmentTable } from '../../components/tables/general/AttachmentTable
import { PartParameterTable } from '../../components/tables/part/PartParameterTable';
import { PartVariantTable } from '../../components/tables/part/PartVariantTable';
import { RelatedPartTable } from '../../components/tables/part/RelatedPartTable';
import { ManufacturerPartTable } from '../../components/tables/purchasing/ManufacturerPartTable';
import { SupplierPartTable } from '../../components/tables/purchasing/SupplierPartTable';
import { SalesOrderTable } from '../../components/tables/sales/SalesOrderTable';
import { StockItemTable } from '../../components/tables/stock/StockItemTable';
@ -155,7 +156,14 @@ export default function PartDetail() {
name: 'manufacturers',
label: t`Manufacturers`,
icon: <IconBuildingFactory2 />,
hidden: !part.purchaseable
hidden: !part.purchaseable,
content: part.pk && (
<ManufacturerPartTable
params={{
part: part.pk
}}
/>
)
},
{
name: 'suppliers',
@ -165,7 +173,7 @@ export default function PartDetail() {
content: part.pk && (
<SupplierPartTable
params={{
part: part.pk ?? -1
part: part.pk
}}
/>
)