From 51babacec01b8c001cacf3b03c21ab4d04559fc0 Mon Sep 17 00:00:00 2001 From: Oliver Date: Wed, 29 Oct 2025 14:28:22 +1100 Subject: [PATCH] [UI] Duplicate part fixes (#10709) * Refactor part duplication - Move duplication items into the form definition * Expand to part variants table --- src/frontend/src/forms/PartForms.tsx | 39 +++++++++++++++++-- src/frontend/src/pages/part/PartDetail.tsx | 38 +++--------------- src/frontend/src/tables/part/PartTable.tsx | 9 ++++- .../src/tables/part/PartVariantTable.tsx | 1 + 4 files changed, 50 insertions(+), 37 deletions(-) diff --git a/src/frontend/src/forms/PartForms.tsx b/src/frontend/src/forms/PartForms.tsx index 7a0a724b0b..d5059f9826 100644 --- a/src/frontend/src/forms/PartForms.tsx +++ b/src/frontend/src/forms/PartForms.tsx @@ -1,5 +1,5 @@ import { t } from '@lingui/core/macro'; -import { IconPackages } from '@tabler/icons-react'; +import { IconBuildingStore, IconCopy, IconPackages } from '@tabler/icons-react'; import { useMemo, useState } from 'react'; import { ApiEndpoints } from '@lib/enums/ApiEndpoints'; @@ -12,8 +12,10 @@ import { useGlobalSettingsState } from '../states/SettingsStates'; * Construct a set of fields for creating / editing a Part instance */ export function usePartFields({ - create = false + create = false, + duplicatePartInstance }: { + duplicatePartInstance?: any; create?: boolean; }): ApiFormFieldSet { const settings = useGlobalSettingsState(); @@ -89,6 +91,7 @@ export function usePartFields({ }; fields.initial_supplier = { + icon: , children: { supplier: { filters: { @@ -106,6 +109,36 @@ export function usePartFields({ }; } + // Additional fields for part duplication + if (create && duplicatePartInstance?.pk) { + fields.duplicate = { + icon: , + children: { + part: { + value: duplicatePartInstance?.pk, + hidden: true + }, + copy_image: { + value: true + }, + copy_bom: { + value: settings.isSet('PART_COPY_BOM'), + hidden: !duplicatePartInstance?.assembly + }, + copy_notes: { + value: true + }, + copy_parameters: { + value: settings.isSet('PART_COPY_PARAMETERS') + }, + copy_tests: { + value: true, + hidden: !duplicatePartInstance?.testable + } + } + }; + } + if (settings.isSet('PART_REVISION_ASSEMBLY_ONLY')) { fields.revision_of.filters['assembly'] = true; } @@ -126,7 +159,7 @@ export function usePartFields({ } return fields; - }, [create, settings]); + }, [create, duplicatePartInstance, settings]); } /** diff --git a/src/frontend/src/pages/part/PartDetail.tsx b/src/frontend/src/pages/part/PartDetail.tsx index a35d50482e..aaa42fecef 100644 --- a/src/frontend/src/pages/part/PartDetail.tsx +++ b/src/frontend/src/pages/part/PartDetail.tsx @@ -47,7 +47,7 @@ import { UserRoles } from '@lib/enums/Roles'; import { apiUrl } from '@lib/functions/Api'; import { getDetailUrl } from '@lib/functions/Navigation'; import { ActionButton } from '@lib/index'; -import type { ApiFormFieldSet, StockOperationProps } from '@lib/types/Forms'; +import type { StockOperationProps } from '@lib/types/Forms'; import AdminButton from '../../components/buttons/AdminButton'; import { PrintingActions } from '../../components/buttons/PrintingActions'; import StarredToggleButton from '../../components/buttons/StarredToggleButton'; @@ -1048,38 +1048,10 @@ export default function PartDetail() { onFormSuccess: refreshInstance }); - const createPartFields = usePartFields({ create: true }); - - const duplicatePartFields: ApiFormFieldSet = useMemo(() => { - return { - ...createPartFields, - duplicate: { - children: { - part: { - value: part.pk, - hidden: true - }, - copy_image: { - value: true - }, - copy_bom: { - value: part.assembly && globalSettings.isSet('PART_COPY_BOM'), - hidden: !part.assembly - }, - copy_notes: { - value: true - }, - copy_parameters: { - value: globalSettings.isSet('PART_COPY_PARAMETERS') - }, - copy_tests: { - value: part.testable, - hidden: !part.testable - } - } - } - }; - }, [createPartFields, globalSettings, part]); + const duplicatePartFields = usePartFields({ + create: true, + duplicatePartInstance: part + }); const duplicatePart = useCreateApiFormModal({ url: ApiEndpoints.part_list, diff --git a/src/frontend/src/tables/part/PartTable.tsx b/src/frontend/src/tables/part/PartTable.tsx index ce5b19359b..7093149492 100644 --- a/src/frontend/src/tables/part/PartTable.tsx +++ b/src/frontend/src/tables/part/PartTable.tsx @@ -336,11 +336,13 @@ function partTableFilters(): TableFilter[] { */ export function PartListTable({ enableImport = true, + basePartInstance, props, defaultPartData }: Readonly<{ enableImport?: boolean; props?: InvenTreeTableProps; + basePartInstance?: any; defaultPartData?: any; }>) { const tableColumns = useMemo(() => partTableColumns(), []); @@ -384,10 +386,15 @@ export function PartListTable({ return defaultPartData ?? props?.params ?? {}; }, [defaultPartData, props?.params]); + const newPartFields = usePartFields({ + create: true, + duplicatePartInstance: basePartInstance + }); + const newPart = useCreateApiFormModal({ url: ApiEndpoints.part_list, title: t`Add Part`, - fields: usePartFields({ create: true }), + fields: newPartFields, initialData: initialPartData, follow: true, modelType: ModelType.part diff --git a/src/frontend/src/tables/part/PartVariantTable.tsx b/src/frontend/src/tables/part/PartVariantTable.tsx index 53fa6ef247..845e654b3b 100644 --- a/src/frontend/src/tables/part/PartVariantTable.tsx +++ b/src/frontend/src/tables/part/PartVariantTable.tsx @@ -43,6 +43,7 @@ export function PartVariantTable({ part }: Readonly<{ part: any }>) { ancestor: part.pk } }} + basePartInstance={part} defaultPartData={{ ...part, variant_of: part.pk,