2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-07-07 06:00:57 +00:00

Enhance "path" columns (#9918)

* Enhance "path" columns

- Show "short" version
- Hovercard for full "pathstring"

* Remove old code

* adjust unit tests
This commit is contained in:
Oliver
2025-07-01 21:31:25 +10:00
committed by GitHub
parent 0f407f7dbb
commit 2ee00c80f5
6 changed files with 67 additions and 36 deletions

View File

@ -21,7 +21,7 @@ import { formatCurrency, formatDate } from '../defaults/formatters';
import { resolveItem } from '../functions/conversion'; import { resolveItem } from '../functions/conversion';
import { useGlobalSettingsState } from '../states/SettingsStates'; import { useGlobalSettingsState } from '../states/SettingsStates';
import type { TableColumn, TableColumnProps } from './Column'; import type { TableColumn, TableColumnProps } from './Column';
import { ProjectCodeHoverCard } from './TableHoverCard'; import { ProjectCodeHoverCard, TableHoverCard } from './TableHoverCard';
// Render a Part instance within a table // Render a Part instance within a table
export function PartColumn({ export function PartColumn({
@ -81,28 +81,57 @@ export function CompanyColumn({
); );
} }
export function LocationColumn(props: TableColumnProps): TableColumn { /**
* Return a column which displays a tree path for a given record.
*/
export function PathColumn(props: TableColumnProps): TableColumn {
return { return {
...props,
accessor: props.accessor ?? 'path',
render: (record: any) => {
const instance = resolveItem(record, props.accessor ?? '');
if (!instance || !instance.name) {
return '-';
}
const name = instance.name ?? '';
const pathstring = instance.pathstring || name;
if (name == pathstring) {
return <Text>{name}</Text>;
}
return (
<TableHoverCard
value={<Text>{instance.name}</Text>}
icon='sitemap'
title={props.title}
extra={[<Text>{instance.pathstring}</Text>]}
/>
);
}
};
}
export function LocationColumn(props: TableColumnProps): TableColumn {
return PathColumn({
accessor: 'location', accessor: 'location',
title: t`Location`, title: t`Location`,
sortable: true, sortable: true,
ordering: 'location', ordering: 'location',
render: (record: any) => {
const location = resolveItem(record, props.accessor ?? '');
if (!location) {
return (
<Text
size='sm'
style={{ fontStyle: 'italic' }}
>{t`No location set`}</Text>
);
}
return <Text size='sm'>{location.pathstring}</Text>;
},
...props ...props
}; });
}
export function CategoryColumn(props: TableColumnProps): TableColumn {
return PathColumn({
accessor: 'category',
title: t`Category`,
sortable: true,
ordering: 'category',
...props
});
} }
export function BooleanColumn(props: TableColumn): TableColumn { export function BooleanColumn(props: TableColumn): TableColumn {

View File

@ -1,6 +1,5 @@
import { t } from '@lingui/core/macro'; import { t } from '@lingui/core/macro';
import { Divider, Group, HoverCard, Stack, Text } from '@mantine/core'; import { Divider, Group, HoverCard, Stack, Text } from '@mantine/core';
import { IconInfoCircle } from '@tabler/icons-react';
import { type ReactNode, useMemo } from 'react'; import { type ReactNode, useMemo } from 'react';
import type { InvenTreeIconType } from '@lib/types/Icons'; import type { InvenTreeIconType } from '@lib/types/Icons';
@ -61,7 +60,10 @@ export function TableHoverCard({
<HoverCard.Dropdown> <HoverCard.Dropdown>
<Stack gap='xs'> <Stack gap='xs'>
<Group gap='xs' justify='left'> <Group gap='xs' justify='left'>
<IconInfoCircle size='16' color='blue' /> <InvenTreeIcon
icon={icon ?? 'info'}
iconProps={{ size: 16, color: iconColor ?? 'blue' }}
/>
<Text fw='bold'>{title}</Text> <Text fw='bold'>{title}</Text>
</Group> </Group>
<Divider /> <Divider />

View File

@ -22,7 +22,12 @@ import {
import { useTable } from '../../hooks/UseTable'; import { useTable } from '../../hooks/UseTable';
import { useUserState } from '../../states/UserState'; import { useUserState } from '../../states/UserState';
import type { TableColumn } from '../Column'; import type { TableColumn } from '../Column';
import { DescriptionColumn, LinkColumn, PartColumn } from '../ColumnRenderers'; import {
CategoryColumn,
DescriptionColumn,
LinkColumn,
PartColumn
} from '../ColumnRenderers';
import { InvenTreeTable, type InvenTreeTableProps } from '../InvenTreeTable'; import { InvenTreeTable, type InvenTreeTableProps } from '../InvenTreeTable';
import { type RowAction, RowEditAction } from '../RowActions'; import { type RowAction, RowEditAction } from '../RowActions';
import { TableHoverCard } from '../TableHoverCard'; import { TableHoverCard } from '../TableHoverCard';
@ -53,11 +58,9 @@ function partTableColumns(): TableColumn[] {
sortable: true sortable: true
}, },
DescriptionColumn({}), DescriptionColumn({}),
{ CategoryColumn({
accessor: 'category', accessor: 'category_detail'
sortable: true, }),
render: (record: any) => record.category_detail?.pathstring
},
{ {
accessor: 'default_location', accessor: 'default_location',
sortable: true, sortable: true,

View File

@ -32,8 +32,8 @@ import type { TableColumn } from '../Column';
import { import {
DateColumn, DateColumn,
DescriptionColumn, DescriptionColumn,
LocationColumn,
PartColumn, PartColumn,
PathColumn,
StatusColumn StatusColumn
} from '../ColumnRenderers'; } from '../ColumnRenderers';
import { StatusFilterOptions } from '../Filter'; import { StatusFilterOptions } from '../Filter';
@ -222,9 +222,10 @@ function stockItemTableColumns({
accessor: 'batch', accessor: 'batch',
sortable: true sortable: true
}, },
LocationColumn({ PathColumn({
hidden: !showLocation, accessor: 'location_detail',
accessor: 'location_detail' title: t`Location`,
hidden: !showLocation
}), }),
{ {
accessor: 'purchase_order', accessor: 'purchase_order',

View File

@ -80,7 +80,7 @@ test('Build Order - Basic Tests', async ({ browser }) => {
await page.getByText('Quantity: 25').waitFor(); await page.getByText('Quantity: 25').waitFor();
await page.getByText('Continuity Checks').waitFor(); await page.getByText('Continuity Checks').waitFor();
await page await page
.getByRole('row', { name: 'Quantity: 16 No location set' }) .getByRole('row', { name: 'Quantity: 16' })
.getByRole('button') .getByRole('button')
.hover(); .hover();
await page.getByText('Add Test Result').waitFor(); await page.getByText('Add Test Result').waitFor();
@ -241,9 +241,7 @@ test('Build Order - Allocation', async ({ browser }) => {
// Expand this row // Expand this row
await cell.click(); await cell.click();
await page.getByRole('cell', { name: '2022-4-27', exact: true }).waitFor(); await page.getByRole('cell', { name: '2022-4-27', exact: true }).waitFor();
await page await page.getByRole('cell', { name: 'Reel Storage', exact: true }).waitFor();
.getByRole('cell', { name: 'Electronics Lab/Reel Storage', exact: true })
.waitFor();
// Navigate to the "Incomplete Outputs" tab // Navigate to the "Incomplete Outputs" tab
await loadTab(page, 'Incomplete Outputs'); await loadTab(page, 'Incomplete Outputs');

View File

@ -225,9 +225,7 @@ test('Parts - Allocations', async ({ browser }) => {
// Expand allocations against BO0001 // Expand allocations against BO0001
await build_order_cell.click(); await build_order_cell.click();
await page.getByRole('cell', { name: '# 3', exact: true }).waitFor(); await page.getByRole('cell', { name: '# 3', exact: true }).waitFor();
await page await page.getByRole('cell', { name: 'Room 101', exact: true }).waitFor();
.getByRole('cell', { name: 'Factory/Office Block/Room 101', exact: true })
.waitFor();
await build_order_cell.click(); await build_order_cell.click();
// Check row options for BO0001 // Check row options for BO0001