diff --git a/src/frontend/lib/types/Tables.tsx b/src/frontend/lib/types/Tables.tsx index ba0675e285..99f02ce06d 100644 --- a/src/frontend/lib/types/Tables.tsx +++ b/src/frontend/lib/types/Tables.tsx @@ -99,6 +99,8 @@ export type TableState = { * @param cellsStyle - The style of the cells in the column * @param extra - Extra data to pass to the render function * @param noContext - Disable context menu for this column + * @param copyable - Enable copy button on hover (uses accessor to get value, or custom function) + * @param copyAccessor - Custom accessor path for copy value (defaults to column accessor) */ export type TableColumnProps = { accessor?: string; @@ -123,6 +125,8 @@ export type TableColumnProps = { extra?: any; noContext?: boolean; style?: MantineStyleProp; + copyable?: boolean | ((record: T) => string); + copyAccessor?: string; }; /** diff --git a/src/frontend/src/components/buttons/CopyButton.tsx b/src/frontend/src/components/buttons/CopyButton.tsx index b9e283daa9..7479054cbc 100644 --- a/src/frontend/src/components/buttons/CopyButton.tsx +++ b/src/frontend/src/components/buttons/CopyButton.tsx @@ -46,7 +46,11 @@ export function CopyButton({ { + e.stopPropagation(); + e.preventDefault(); + copy(); + }} variant='transparent' size={size ?? 'sm'} > diff --git a/src/frontend/src/tables/CopyableCell.tsx b/src/frontend/src/tables/CopyableCell.tsx new file mode 100644 index 0000000000..4d7fc94712 --- /dev/null +++ b/src/frontend/src/tables/CopyableCell.tsx @@ -0,0 +1,40 @@ +import { Group } from '@mantine/core'; +import { useState } from 'react'; +import { CopyButton } from '../components/buttons/CopyButton'; + +/** + * A wrapper component that adds a copy button to cell content on hover + * This component is used to make table cells copyable without adding visual clutter + * + * @param children - The cell content to render + * @param value - The value to copy when the copy button is clicked + */ +export function CopyableCell({ + children, + value +}: Readonly<{ + children: React.ReactNode; + value: string; +}>) { + const [isHovered, setIsHovered] = useState(false); + + return ( + setIsHovered(true)} + onMouseLeave={() => setIsHovered(false)} + justify='space-between' + > + {children} + {isHovered && value != null && ( + e.stopPropagation()} + onKeyDown={(e) => e.stopPropagation()} + > + + + )} + + ); +} diff --git a/src/frontend/src/tables/InvenTreeTable.tsx b/src/frontend/src/tables/InvenTreeTable.tsx index d84c237721..ca3d43e35b 100644 --- a/src/frontend/src/tables/InvenTreeTable.tsx +++ b/src/frontend/src/tables/InvenTreeTable.tsx @@ -33,6 +33,7 @@ import { hashString } from '../functions/tables'; import { useLocalState } from '../states/LocalState'; import { useUserSettingsState } from '../states/SettingsStates'; import { useStoredTableState } from '../states/StoredTableState'; +import { CopyableCell } from './CopyableCell'; import InvenTreeTableHeader from './InvenTreeTableHeader'; const ACTIONS_COLUMN_ACCESSOR: string = '--actions--'; @@ -264,11 +265,36 @@ export function InvenTreeTable>({ ? false : (tableState.hiddenColumns?.includes(col.accessor) ?? false); + // Wrap the render function with CopyableCell if copyable is enabled + const originalRender = col.render; + let wrappedRender = originalRender; + + if (col.copyable) { + wrappedRender = (record: any, index?: number) => { + const content = + originalRender?.(record, index) ?? + resolveItem(record, col.accessor); + + // Determine the value to copy, ensuring it is always a string + let rawCopyValue: unknown; + if (typeof col.copyable === 'function') { + rawCopyValue = col.copyable(record); + } else { + const accessor = col.copyAccessor ?? col.accessor; + rawCopyValue = resolveItem(record, accessor); + } + const copyValue = rawCopyValue == null ? '' : String(rawCopyValue); + + return {content}; + }; + } + return { ...col, hidden: hidden, resizable: col.resizable ?? true, title: col.title ?? fieldNames[col.accessor] ?? `${col.accessor}`, + render: wrappedRender, cellsStyle: (record: any, index: number) => { const width = (col as any).minWidth ?? 100; return { diff --git a/src/frontend/src/tables/purchasing/PurchaseOrderLineItemTable.tsx b/src/frontend/src/tables/purchasing/PurchaseOrderLineItemTable.tsx index 3decd7c824..2f4198e459 100644 --- a/src/frontend/src/tables/purchasing/PurchaseOrderLineItemTable.tsx +++ b/src/frontend/src/tables/purchasing/PurchaseOrderLineItemTable.tsx @@ -237,7 +237,8 @@ export function PurchaseOrderLineItemTable({ title: t`Supplier Code`, switchable: false, sortable: true, - ordering: 'SKU' + ordering: 'SKU', + copyable: true }, LinkColumn({ accessor: 'supplier_part_detail.link',