From e55a6e78d25454f78c33cd95470dc3fb429a4481 Mon Sep 17 00:00:00 2001 From: Oliver Date: Mon, 10 Aug 2026 15:53:03 +1000 Subject: [PATCH] [UI] Bulk Delete Enhancements (#12601) * Add optional filtering for bulkDelete * Enhanced pre-form content --- src/frontend/lib/types/Tables.tsx | 2 + .../tables/InvenTreeTableHeader.tsx | 40 ++++++++++++++++++- src/frontend/src/tables/bom/BomTable.tsx | 4 ++ 3 files changed, 44 insertions(+), 2 deletions(-) diff --git a/src/frontend/lib/types/Tables.tsx b/src/frontend/lib/types/Tables.tsx index 44bb08cda2..f058617752 100644 --- a/src/frontend/lib/types/Tables.tsx +++ b/src/frontend/lib/types/Tables.tsx @@ -168,6 +168,7 @@ export type RowViewProps = RowAction & RowModelProps & RowViewBehaviorProps; * @param tableState : TableState - State manager for the table * @param defaultSortColumn : string - Default column to sort by * @param noRecordsText : string - Text to display when no records are found + * @param bulkDeleteFilter : (record: any) => boolean - Callback function to determine if a record is eligible for bulk deletion * @param enableBulkDelete : boolean - Enable bulk deletion of records * @param enableDownload : boolean - Enable download actions * @param enableFilters : boolean - Enable filter actions @@ -198,6 +199,7 @@ export type InvenTreeTableProps = { params?: any; defaultSortColumn?: string; noRecordsText?: string; + bulkDeleteFilter?: (record: T) => boolean; enableBulkDelete?: boolean; enableDownload?: boolean; enableFilters?: boolean; diff --git a/src/frontend/src/components/tables/InvenTreeTableHeader.tsx b/src/frontend/src/components/tables/InvenTreeTableHeader.tsx index 11d019ef7e..666c269754 100644 --- a/src/frontend/src/components/tables/InvenTreeTableHeader.tsx +++ b/src/frontend/src/components/tables/InvenTreeTableHeader.tsx @@ -9,6 +9,7 @@ import { Paper, Space, Stack, + Text, Tooltip } from '@mantine/core'; import { @@ -102,6 +103,38 @@ export default function InvenTreeTableHeader({ searchTerm: tableState.searchTerm }); + const bulkDeleteIds: number[] = useMemo(() => { + if (!tableProps.enableBulkDelete) { + return []; + } + + const selectedRecords = tableState.selectedRecords ?? []; + const filterFn = tableProps.bulkDeleteFilter ?? (() => true); + + return selectedRecords + .filter((record) => filterFn(record)) + .map( + (record) => resolveItem(record, tableState.idAccessor || 'pk') as number + ); + }, [ + tableProps.enableBulkDelete, + tableProps.bulkDeleteFilter, + tableState.idAccessor, + tableState.selectedRecords + ]); + + const deleteItemsText = useMemo(() => { + const N = bulkDeleteIds.length; + + if (N === 0) { + return t`No items selected`; + } else if (N === 1) { + return t`1 selected item will be deleted`; + } else { + return t`${N} selected items will be deleted`; + } + }, [bulkDeleteIds]); + const deleteRecords = useDeleteApiFormModal({ url: tableUrl ?? '', title: t`Delete Selected Items`, @@ -110,11 +143,14 @@ export default function InvenTreeTableHeader({ color='red' title={t`Are you sure you want to delete the selected items?`} > - {t`This action cannot be undone`} + + {deleteItemsText} + {t`This action cannot be undone`} + ), initialData: { - items: tableState.selectedIds + items: bulkDeleteIds }, fields: { items: { diff --git a/src/frontend/src/tables/bom/BomTable.tsx b/src/frontend/src/tables/bom/BomTable.tsx index e570475eb8..fb38da1041 100644 --- a/src/frontend/src/tables/bom/BomTable.tsx +++ b/src/frontend/src/tables/bom/BomTable.tsx @@ -742,6 +742,10 @@ export function BomTable({ enableSelection: isEditing && !isLocked, enableBulkDelete: isEditing && !isLocked && user.hasDeleteRole(UserRoles.bom), + bulkDeleteFilter: (record: any) => { + // If the BOM item is defined for a different parent, then it cannot be deleted + return record.part === partId; + }, enableDownload: true, rowExpansion: isEditing ? undefined : rowExpansion }}