From 7227670142d4ba8cbed2d5e9215f3b342541ca3e Mon Sep 17 00:00:00 2001 From: Oliver Date: Wed, 20 Mar 2024 23:00:09 +1100 Subject: [PATCH] Enable "link" column for tables (#6765) - Have to handle click events cleverly --- .../src/components/details/DetailsImage.tsx | 18 ++++------ src/frontend/src/functions/events.tsx | 6 ++++ src/frontend/src/tables/ColumnRenderers.tsx | 35 ++++++++++++++++--- src/frontend/src/tables/RowActions.tsx | 9 ++--- .../src/tables/part/ParametricPartTable.tsx | 5 ++- src/frontend/src/tables/part/PartTable.tsx | 2 +- .../purchasing/ManufacturerPartTable.tsx | 2 +- .../purchasing/PurchaseOrderLineItemTable.tsx | 8 ++--- .../tables/purchasing/SupplierPartTable.tsx | 2 +- 9 files changed, 54 insertions(+), 33 deletions(-) create mode 100644 src/frontend/src/functions/events.tsx diff --git a/src/frontend/src/components/details/DetailsImage.tsx b/src/frontend/src/components/details/DetailsImage.tsx index 6aed0729b9..dc0dd6b808 100644 --- a/src/frontend/src/components/details/DetailsImage.tsx +++ b/src/frontend/src/components/details/DetailsImage.tsx @@ -17,6 +17,7 @@ import { useMemo, useState } from 'react'; import { api } from '../../App'; import { UserRoles } from '../../enums/Roles'; +import { cancelEvent } from '../../functions/events'; import { InvenTreeIcon } from '../../functions/icons'; import { useUserState } from '../../states/UserState'; import { PartThumbTable } from '../../tables/part/PartThumbTable'; @@ -267,9 +268,8 @@ function ImageActionButtons({ size="lg" tooltipAlignment="top" onClick={(event: any) => { - event?.preventDefault(); - event?.stopPropagation(); - event?.nativeEvent?.stopImmediatePropagation(); + cancelEvent(event); + modals.open({ title: {t`Select Image`}, size: 'xxl', @@ -288,9 +288,7 @@ function ImageActionButtons({ size="lg" tooltipAlignment="top" onClick={(event: any) => { - event?.preventDefault(); - event?.stopPropagation(); - event?.nativeEvent?.stopImmediatePropagation(); + cancelEvent(event); modals.open({ title: {t`Upload Image`}, children: ( @@ -310,9 +308,7 @@ function ImageActionButtons({ size="lg" tooltipAlignment="top" onClick={(event: any) => { - event?.preventDefault(); - event?.stopPropagation(); - event?.nativeEvent?.stopImmediatePropagation(); + cancelEvent(event); removeModal(apiPath, setImage); }} /> @@ -349,9 +345,7 @@ export function DetailsImage(props: DetailImageProps) { }, [props.imageActions]); const expandImage = (event: any) => { - event?.preventDefault(); - event?.stopPropagation(); - event?.nativeEvent?.stopImmediatePropagation(); + cancelEvent(event); modals.open({ children: , withCloseButton: false diff --git a/src/frontend/src/functions/events.tsx b/src/frontend/src/functions/events.tsx new file mode 100644 index 0000000000..a7daee5440 --- /dev/null +++ b/src/frontend/src/functions/events.tsx @@ -0,0 +1,6 @@ +// Helper function to cancel event propagation +export function cancelEvent(event: any) { + event?.preventDefault(); + event?.stopPropagation(); + event?.nativeEvent?.stopImmediatePropagation(); +} diff --git a/src/frontend/src/tables/ColumnRenderers.tsx b/src/frontend/src/tables/ColumnRenderers.tsx index 7f7369a321..acd89d48c4 100644 --- a/src/frontend/src/tables/ColumnRenderers.tsx +++ b/src/frontend/src/tables/ColumnRenderers.tsx @@ -2,6 +2,7 @@ * Common rendering functions for table column data. */ import { t } from '@lingui/macro'; +import { Anchor } from '@mantine/core'; import { Thumbnail } from '../components/images/Thumbnail'; import { ProgressBar } from '../components/items/ProgressBar'; @@ -10,6 +11,7 @@ import { TableStatusRenderer } from '../components/render/StatusRenderer'; import { RenderOwner } from '../components/render/User'; import { formatCurrency, renderDate } from '../defaults/formatters'; import { ModelType } from '../enums/ModelType'; +import { cancelEvent } from '../functions/events'; import { TableColumn } from './Column'; import { ProjectCodeHoverCard } from './TableHoverCard'; @@ -55,11 +57,36 @@ export function DescriptionColumn({ }; } -export function LinkColumn(): TableColumn { +export function LinkColumn({ + accessor = 'link' +}: { + accessor?: string; +}): TableColumn { return { - accessor: 'link', - sortable: false - // TODO: Custom URL hyperlink renderer? + accessor: accessor, + sortable: false, + render: (record: any) => { + let url = record[accessor]; + + if (!url) { + return '-'; + } + + return ( + { + cancelEvent(event); + + window.open(url, '_blank', 'noopener,noreferrer'); + }} + > + {url} + + ); + } }; } diff --git a/src/frontend/src/tables/RowActions.tsx b/src/frontend/src/tables/RowActions.tsx index 9835d8761a..1b35741fe4 100644 --- a/src/frontend/src/tables/RowActions.tsx +++ b/src/frontend/src/tables/RowActions.tsx @@ -4,6 +4,7 @@ import { Menu } from '@mantine/core'; import { IconCopy, IconDots, IconEdit, IconTrash } from '@tabler/icons-react'; import { ReactNode, useMemo, useState } from 'react'; +import { cancelEvent } from '../functions/events'; import { notYetImplemented } from '../functions/notifications'; // Type definition for a table row action @@ -93,9 +94,7 @@ export function RowActions({ // Prevent default event handling // Ref: https://icflorescu.github.io/mantine-datatable/examples/links-or-buttons-inside-clickable-rows-or-cells function openMenu(event: any) { - event?.preventDefault(); - event?.stopPropagation(); - event?.nativeEvent?.stopImmediatePropagation(); + cancelEvent(event); setOpened(!opened); } @@ -118,9 +117,7 @@ export function RowActions({ icon={action.icon} onClick={(event) => { // Prevent clicking on the action from selecting the row itself - event?.preventDefault(); - event?.stopPropagation(); - event?.nativeEvent?.stopImmediatePropagation(); + cancelEvent(event); if (action.onClick) { action.onClick(); diff --git a/src/frontend/src/tables/part/ParametricPartTable.tsx b/src/frontend/src/tables/part/ParametricPartTable.tsx index e0f3140491..c842efde04 100644 --- a/src/frontend/src/tables/part/ParametricPartTable.tsx +++ b/src/frontend/src/tables/part/ParametricPartTable.tsx @@ -11,6 +11,7 @@ import { YesNoButton } from '../../components/items/YesNoButton'; import { ApiEndpoints } from '../../enums/ApiEndpoints'; import { ModelType } from '../../enums/ModelType'; import { UserRoles } from '../../enums/Roles'; +import { cancelEvent } from '../../functions/events'; import { useCreateApiFormModal, useEditApiFormModal @@ -60,9 +61,7 @@ function ParameterCell({ } const handleClick = useCallback((event: any) => { - event?.preventDefault(); - event?.stopPropagation(); - event?.nativeEvent?.stopImmediatePropagation(); + cancelEvent(event); onEdit(); }, []); diff --git a/src/frontend/src/tables/part/PartTable.tsx b/src/frontend/src/tables/part/PartTable.tsx index fc3f002461..856e88524b 100644 --- a/src/frontend/src/tables/part/PartTable.tsx +++ b/src/frontend/src/tables/part/PartTable.tsx @@ -162,7 +162,7 @@ function partTableColumns(): TableColumn[] { render: (record: any) => formatPriceRange(record.pricing_min, record.pricing_max) }, - LinkColumn() + LinkColumn({}) ]; } diff --git a/src/frontend/src/tables/purchasing/ManufacturerPartTable.tsx b/src/frontend/src/tables/purchasing/ManufacturerPartTable.tsx index 0955afe3d5..3c8cafe387 100644 --- a/src/frontend/src/tables/purchasing/ManufacturerPartTable.tsx +++ b/src/frontend/src/tables/purchasing/ManufacturerPartTable.tsx @@ -54,7 +54,7 @@ export function ManufacturerPartTable({ params }: { params: any }): ReactNode { sortable: true }, DescriptionColumn({}), - LinkColumn() + LinkColumn({}) ]; }, [params]); diff --git a/src/frontend/src/tables/purchasing/PurchaseOrderLineItemTable.tsx b/src/frontend/src/tables/purchasing/PurchaseOrderLineItemTable.tsx index 85f20488ab..ecac9aef13 100644 --- a/src/frontend/src/tables/purchasing/PurchaseOrderLineItemTable.tsx +++ b/src/frontend/src/tables/purchasing/PurchaseOrderLineItemTable.tsx @@ -26,6 +26,7 @@ import { useUserState } from '../../states/UserState'; import { CurrencyColumn, LinkColumn, + NoteColumn, ReferenceColumn, TargetDateColumn, TotalPriceColumn @@ -177,11 +178,8 @@ export function PurchaseOrderLineItemTable({ ? RenderStockLocation({ instance: record.destination_detail }) : '-' }, - { - accessor: 'notes', - title: t`Notes` - }, - LinkColumn() + NoteColumn(), + LinkColumn({}) ]; }, [orderId, user]); diff --git a/src/frontend/src/tables/purchasing/SupplierPartTable.tsx b/src/frontend/src/tables/purchasing/SupplierPartTable.tsx index 572c1bfbfb..9bc820c4a3 100644 --- a/src/frontend/src/tables/purchasing/SupplierPartTable.tsx +++ b/src/frontend/src/tables/purchasing/SupplierPartTable.tsx @@ -122,7 +122,7 @@ export function SupplierPartTable({ params }: { params: any }): ReactNode { ); } }, - LinkColumn(), + LinkColumn({}), NoteColumn(), { accessor: 'available',