diff --git a/src/frontend/src/components/details/Details.tsx b/src/frontend/src/components/details/Details.tsx index e1cc70e3d4..438f63f036 100644 --- a/src/frontend/src/components/details/Details.tsx +++ b/src/frontend/src/components/details/Details.tsx @@ -3,6 +3,7 @@ import { Anchor, Badge, Group, + type MantineColor, Paper, Skeleton, Stack, @@ -298,9 +299,12 @@ function TableAnchorValue(props: Readonly) { value = data?.name; } + let color: MantineColor | undefined = undefined; + if (value === undefined) { value = data?.name ?? props.field_data?.backup_value ?? t`No name defined`; make_link = false; + color = 'red'; } return ( @@ -310,7 +314,7 @@ function TableAnchorValue(props: Readonly) { {value} ) : ( - {value} + {value} )} ); diff --git a/src/frontend/src/main.tsx b/src/frontend/src/main.tsx index b8377c396c..49892ede33 100644 --- a/src/frontend/src/main.tsx +++ b/src/frontend/src/main.tsx @@ -11,6 +11,7 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; import 'react-grid-layout/css/styles.css'; import 'react-resizable/css/styles.css'; +import './styles/overrides.css'; import { api } from './App'; import type { HostList } from './states/states'; diff --git a/src/frontend/src/styles/overrides.css b/src/frontend/src/styles/overrides.css new file mode 100644 index 0000000000..892b221219 --- /dev/null +++ b/src/frontend/src/styles/overrides.css @@ -0,0 +1,5 @@ +/* mantine-datatable overrides */ +.mantine-datatable-pointer-cursor, +.mantine-datatable-context-menu-cursor { + cursor: pointer; +} diff --git a/src/frontend/src/tables/InvenTreeTable.tsx b/src/frontend/src/tables/InvenTreeTable.tsx index bceb9a9d2f..349e1b30fb 100644 --- a/src/frontend/src/tables/InvenTreeTable.tsx +++ b/src/frontend/src/tables/InvenTreeTable.tsx @@ -1,7 +1,10 @@ import { t } from '@lingui/macro'; -import { Box, Stack } from '@mantine/core'; +import { Box, type MantineStyleProp, Stack } from '@mantine/core'; import { useQuery } from '@tanstack/react-query'; -import { useContextMenu } from 'mantine-contextmenu'; +import { + type ContextMenuItemOptions, + useContextMenu +} from 'mantine-contextmenu'; import { DataTable, type DataTableCellClickHandler, @@ -12,6 +15,7 @@ import type React from 'react'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { useNavigate } from 'react-router-dom'; +import { IconArrowRight } from '@tabler/icons-react'; import { api } from '../App'; import { Boundary } from '../components/Boundary'; import type { ApiFormFieldSet } from '../components/forms/fields/ApiFormField'; @@ -86,7 +90,7 @@ export type InvenTreeTableProps = { onRowClick?: (record: T, index: number, event: any) => void; onCellClick?: DataTableCellClickHandler; modelType?: ModelType; - rowStyle?: (record: T, index: number) => any; + rowStyle?: (record: T, index: number) => MantineStyleProp | undefined; modelField?: string; onCellContextMenu?: (record: T, event: any) => void; minHeight?: number; @@ -568,6 +572,10 @@ export function InvenTreeTable>({ [props.onRowClick, props.onCellClick] ); + const supportsContextMenu = useMemo(() => { + return !!props.onCellContextMenu || !!props.rowActions || !!props.modelType; + }, [props.onCellContextMenu, props.rowActions, props.modelType]); + // Callback when a cell is right-clicked const handleCellContextMenu = ({ record, @@ -583,9 +591,13 @@ export function InvenTreeTable>({ } if (props.onCellContextMenu) { return props.onCellContextMenu(record, event); - } else if (props.rowActions) { - const empty = () => {}; - const items = props.rowActions(record).map((action) => ({ + } + + const empty = () => {}; + let items: ContextMenuItemOptions[] = []; + + if (props.rowActions) { + items = props.rowActions(record).map((action) => ({ key: action.title ?? '', title: action.title ?? '', color: action.color, @@ -594,10 +606,25 @@ export function InvenTreeTable>({ hidden: action.hidden, disabled: action.disabled })); - return showContextMenu(items)(event); - } else { - return showContextMenu([])(event); } + + if (props.modelType) { + // Add action to navigate to the detail view + const accessor = props.modelField ?? 'pk'; + const pk = resolveItem(record, accessor); + const url = getDetailUrl(props.modelType, pk); + items.push({ + key: 'detail', + title: t`View details`, + icon: , + onClick: (event: any) => { + cancelEvent(event); + navigateToLink(url, navigate, event); + } + }); + } + + return showContextMenu(items)(event); }; // pagination refresth table if pageSize changes @@ -643,6 +670,14 @@ export function InvenTreeTable>({ return optionalParamsa; }, [tableProps.enablePagination]); + const supportsCellClick = useMemo(() => { + return !!( + tableProps.onCellClick || + tableProps.onRowClick || + tableProps.modelType + ); + }, [tableProps.onCellClick, tableProps.onRowClick, tableProps.modelType]); + return ( <> @@ -683,12 +718,12 @@ export function InvenTreeTable>({ enableSelection ? onSelectedRecordsChange : undefined } rowExpansion={rowExpansion} - rowStyle={tableProps.rowStyle} + // rowStyle={rowStyleCallback} fetching={isFetching} noRecordsText={missingRecordsText} records={tableState.records} columns={dataColumns} - onCellClick={handleCellClick} + onCellClick={supportsCellClick ? handleCellClick : undefined} noHeader={tableProps.noHeader ?? false} defaultColumnProps={{ noWrap: true, @@ -698,7 +733,9 @@ export function InvenTreeTable>({ overflow: 'hidden' }) }} - onCellContextMenu={handleCellContextMenu} + onCellContextMenu={ + supportsContextMenu ? handleCellContextMenu : undefined + } {...optionalParams} />