mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-10-31 13:15:43 +00:00 
			
		
		
		
	[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:
		| @@ -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 | ||||
|   | ||||
| @@ -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', | ||||
|   | ||||
| @@ -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` | ||||
|   | ||||
| @@ -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 | ||||
|       }} | ||||
|     /> | ||||
|   ); | ||||
| } | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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` | ||||
|     }, | ||||
|     { | ||||
|   | ||||
| @@ -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 | ||||
|  */ | ||||
|   | ||||
| @@ -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 | ||||
|             }} | ||||
|           /> | ||||
|         ) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user