From be1820fb948f404e4df6b13aa6fd372964f1ffd1 Mon Sep 17 00:00:00 2001 From: Oliver Date: Tue, 19 Dec 2023 15:28:27 +1100 Subject: [PATCH] [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 --- .../src/components/tables/ColumnRenderers.tsx | 6 + .../tables/part/PartParameterTable.tsx | 19 +-- .../part/PartParameterTemplateTable.tsx | 7 +- .../purchasing/ManufacturerPartTable.tsx | 119 ++++++++++++++++++ .../tables/purchasing/SupplierPartTable.tsx | 13 +- .../tables/stock/StockItemTable.tsx | 18 +-- src/frontend/src/forms/CompanyForms.tsx | 14 +++ src/frontend/src/pages/part/PartDetail.tsx | 12 +- 8 files changed, 160 insertions(+), 48 deletions(-) create mode 100644 src/frontend/src/components/tables/purchasing/ManufacturerPartTable.tsx diff --git a/src/frontend/src/components/tables/ColumnRenderers.tsx b/src/frontend/src/components/tables/ColumnRenderers.tsx index daf81c011c..0913f87efa 100644 --- a/src/frontend/src/components/tables/ColumnRenderers.tsx +++ b/src/frontend/src/components/tables/ColumnRenderers.tsx @@ -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 ; +} + export function BooleanColumn({ accessor, title diff --git a/src/frontend/src/components/tables/part/PartParameterTable.tsx b/src/frontend/src/components/tables/part/PartParameterTable.tsx index ffaa672522..4a04964521 100644 --- a/src/frontend/src/components/tables/part/PartParameterTable.tsx +++ b/src/frontend/src/components/tables/part/PartParameterTable.tsx @@ -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 ( - - - {part?.full_name} - - ); - } + render: (record: any) => PartColumn(record?.part_detail) }, { accessor: 'name', diff --git a/src/frontend/src/components/tables/part/PartParameterTemplateTable.tsx b/src/frontend/src/components/tables/part/PartParameterTemplateTable.tsx index dc028c0db3..de3fbff064 100644 --- a/src/frontend/src/components/tables/part/PartParameterTemplateTable.tsx +++ b/src/frontend/src/components/tables/part/PartParameterTemplateTable.tsx @@ -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` diff --git a/src/frontend/src/components/tables/purchasing/ManufacturerPartTable.tsx b/src/frontend/src/components/tables/purchasing/ManufacturerPartTable.tsx new file mode 100644 index 0000000000..f5c5c56265 --- /dev/null +++ b/src/frontend/src/components/tables/purchasing/ManufacturerPartTable.tsx @@ -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 ( + + ); + } + }, + { + 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 ( + + ); +} diff --git a/src/frontend/src/components/tables/purchasing/SupplierPartTable.tsx b/src/frontend/src/components/tables/purchasing/SupplierPartTable.tsx index 3ac913ef14..b8456df01e 100644 --- a/src/frontend/src/components/tables/purchasing/SupplierPartTable.tsx +++ b/src/frontend/src/components/tables/purchasing/SupplierPartTable.tsx @@ -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 ( - - ); - } + 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 diff --git a/src/frontend/src/components/tables/stock/StockItemTable.tsx b/src/frontend/src/components/tables/stock/StockItemTable.tsx index da2fc6d1a2..c2ab5bedb0 100644 --- a/src/frontend/src/components/tables/stock/StockItemTable.tsx +++ b/src/frontend/src/components/tables/stock/StockItemTable.tsx @@ -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 ( - - - {part?.full_name} - - ); - } + render: (record: any) => PartColumn(record?.part_detail) }, { accessor: 'part_detail.description', sortable: false, - title: t`Description` }, { diff --git a/src/frontend/src/forms/CompanyForms.tsx b/src/frontend/src/forms/CompanyForms.tsx index 344b6056b6..0477367a13 100644 --- a/src/frontend/src/forms/CompanyForms.tsx +++ b/src/frontend/src/forms/CompanyForms.tsx @@ -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 */ diff --git a/src/frontend/src/pages/part/PartDetail.tsx b/src/frontend/src/pages/part/PartDetail.tsx index 7efe90d47c..6ef88f759f 100644 --- a/src/frontend/src/pages/part/PartDetail.tsx +++ b/src/frontend/src/pages/part/PartDetail.tsx @@ -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: , - hidden: !part.purchaseable + hidden: !part.purchaseable, + content: part.pk && ( + + ) }, { name: 'suppliers', @@ -165,7 +173,7 @@ export default function PartDetail() { content: part.pk && ( )