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',