2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-09-13 14:11:37 +00:00

Column min width (#10204)

* Support minimum column width

* Adjust DescriptionColumn and StatusColumn

* Column refactoring

* Refactor PartColumn

* Refactor LineItemsProgerssColumn

* Tweaks
This commit is contained in:
Oliver
2025-08-20 21:49:27 +10:00
committed by GitHub
parent 76dfd62f84
commit ed31503d3b
30 changed files with 170 additions and 181 deletions

View File

@@ -91,6 +91,7 @@ export type TableState = {
* @param filter - A custom filter function
* @param filtering - Whether the column is filterable
* @param width - The width of the column
* @param minWidth - The minimum width of the column
* @param resizable - Whether the column is resizable (defaults to true)
* @param noWrap - Whether the column should wrap
* @param ellipsis - Whether the column should be ellipsized
@@ -113,6 +114,7 @@ export type TableColumnProps<T = any> = {
filter?: any;
filtering?: boolean;
width?: number;
minWidth?: string | number;
resizable?: boolean;
noWrap?: boolean;
ellipsis?: boolean;
@@ -120,6 +122,7 @@ export type TableColumnProps<T = any> = {
cellsStyle?: any;
extra?: any;
noContext?: boolean;
style?: MantineStyleProp;
};
/**

View File

@@ -14,7 +14,7 @@ import { useSupplierPartFields } from '../../forms/CompanyForms';
import { usePurchaseOrderFields } from '../../forms/PurchaseOrderForms';
import { useCreateApiFormModal } from '../../hooks/UseForm';
import useWizard from '../../hooks/UseWizard';
import { PartColumn } from '../../tables/ColumnRenderers';
import { RenderPartColumn } from '../../tables/ColumnRenderers';
import RemoveRowButton from '../buttons/RemoveRowButton';
import { StandaloneField } from '../forms/StandaloneField';
import Expand from '../items/Expand';
@@ -133,7 +133,7 @@ function SelectPartsStep({
render: (record: PartOrderRecord) => (
<Tooltip label={record.part?.description}>
<Paper p='xs'>
<PartColumn part={record.part} />
<RenderPartColumn part={record.part} />
</Paper>
</Tooltip>
)

View File

@@ -36,7 +36,7 @@ import {
useSerialNumberGenerator
} from '../hooks/UseGenerator';
import { useGlobalSettingsState } from '../states/SettingsStates';
import { PartColumn } from '../tables/ColumnRenderers';
import { RenderPartColumn } from '../tables/ColumnRenderers';
/**
* Field set for BuildOrder forms
@@ -248,7 +248,7 @@ function BuildOutputFormRow({
<>
<Table.Tr>
<Table.Td>
<PartColumn part={record.part_detail} />
<RenderPartColumn part={record.part_detail} />
</Table.Td>
<Table.Td>
<TableFieldErrorWrapper props={props} errorKey='output'>
@@ -541,7 +541,7 @@ function BuildAllocateLineRow({
return (
<Table.Tr key={`table-row-${record.pk}`}>
<Table.Td>
<PartColumn part={record.part_detail} />
<RenderPartColumn part={record.part_detail} />
</Table.Td>
<Table.Td>
<ProgressBar
@@ -704,7 +704,7 @@ function BuildConsumeItemRow({
return (
<Table.Tr key={`table-row-${record.pk}`}>
<Table.Td>
<PartColumn part={record.part_detail} />
<RenderPartColumn part={record.part_detail} />
</Table.Td>
<Table.Td>
<RenderStockItem instance={record.stock_item_detail} />
@@ -807,7 +807,7 @@ function BuildConsumeLineRow({
return (
<Table.Tr key={`table-row-${record.pk}`}>
<Table.Td>
<PartColumn part={record.part_detail} />
<RenderPartColumn part={record.part_detail} />
</Table.Td>
<Table.Td>
{remaining <= 0 ? (

View File

@@ -23,7 +23,7 @@ import type {
import type { TableFieldRowProps } from '../components/forms/fields/TableField';
import { useCreateApiFormModal } from '../hooks/UseForm';
import { useGlobalSettingsState } from '../states/SettingsStates';
import { PartColumn } from '../tables/ColumnRenderers';
import { RenderPartColumn } from '../tables/ColumnRenderers';
export function useSalesOrderFields({
duplicateOrderId
@@ -197,7 +197,7 @@ function SalesOrderAllocateLineRow({
return (
<Table.Tr key={`table-row-${props.idx}-${record.pk}`}>
<Table.Td>
<PartColumn part={record.part_detail} />
<RenderPartColumn part={record.part_detail} />
</Table.Td>
<Table.Td>
<ProgressBar

View File

@@ -136,13 +136,11 @@ export default function BomPricingPanel({
const columns: TableColumn[] = useMemo(() => {
return [
{
PartColumn({
accessor: 'name',
title: t`Component`,
sortable: true,
switchable: false,
render: (record: any) => PartColumn({ part: record.sub_part_detail })
},
part: 'sub_part_detail'
}),
{
accessor: 'quantity',
title: t`Quantity`,

View File

@@ -25,13 +25,11 @@ export default function VariantPricingPanel({
const columns: TableColumn[] = useMemo(() => {
return [
{
accessor: 'name',
PartColumn({
title: t`Variant Part`,
sortable: true,
switchable: false,
render: (record: any) => PartColumn({ part: record, full_name: true })
},
part: '',
full_name: true
}),
{
accessor: 'pricing_min',
title: t`Minimum Price`,

View File

@@ -30,15 +30,24 @@ import {
} from '../states/SettingsStates';
import { ProjectCodeHoverCard, TableHoverCard } from './TableHoverCard';
// Render a Part instance within a table
export function PartColumn({
export type PartColumnProps = TableColumnProps & {
part?: string;
full_name?: boolean;
};
// Extract rendering function for Part column
export function RenderPartColumn({
part,
full_name
}: {
part: any;
full_name?: boolean;
}) {
return part ? (
if (!part) {
return <Skeleton />;
}
return (
<Group justify='space-between' wrap='nowrap'>
<Thumbnail
src={part?.thumbnail ?? part?.image}
@@ -63,11 +72,32 @@ export function PartColumn({
)}
</Group>
</Group>
) : (
<Skeleton />
);
}
// Render a Part instance within a table
export function PartColumn(props: PartColumnProps): TableColumn {
return {
accessor: 'part',
title: t`Part`,
sortable: true,
switchable: false,
minWidth: '175px',
render: (record: any) => {
const part =
props.part === ''
? record
: resolveItem(record, props.part ?? props.accessor ?? 'part_detail');
return RenderPartColumn({
part: part,
full_name: props.full_name ?? false
});
},
...props
};
}
export function CompanyColumn({
company
}: {
@@ -146,6 +176,7 @@ export function LocationColumn(props: TableColumnProps): TableColumn {
title: t`Location`,
sortable: true,
ordering: 'location',
minWidth: '150px',
...props
});
} else {
@@ -154,6 +185,7 @@ export function LocationColumn(props: TableColumnProps): TableColumn {
title: t`Location`,
sortable: true,
ordering: 'location',
minWidth: '125px',
...props
});
}
@@ -192,6 +224,7 @@ export function CategoryColumn(props: TableColumnProps): TableColumn {
title: t`Category`,
sortable: true,
ordering: 'category',
minWidth: '150px',
...props
});
} else {
@@ -200,6 +233,7 @@ export function CategoryColumn(props: TableColumnProps): TableColumn {
title: t`Category`,
sortable: true,
ordering: 'category',
minWidth: '125px',
...props
});
}
@@ -209,6 +243,7 @@ export function BooleanColumn(props: TableColumn): TableColumn {
return {
sortable: true,
switchable: true,
minWidth: '75px',
render: (record: any) => (
<Center>
<YesNoButton value={resolveItem(record, props.accessor ?? '')} />
@@ -234,7 +269,7 @@ export function DescriptionColumn(props: TableColumnProps): TableColumn {
title: t`Description`,
sortable: false,
switchable: true,
width: 300,
minWidth: '200px',
...props
};
}
@@ -291,17 +326,19 @@ export function NoteColumn(props: TableColumnProps): TableColumn {
};
}
export function LineItemsProgressColumn(): TableColumn {
export function LineItemsProgressColumn(props: TableColumnProps): TableColumn {
return {
accessor: 'line_items',
sortable: true,
minWidth: 125,
render: (record: any) => (
<ProgressBar
progressLabel={true}
value={record.completed_lines}
maximum={record.line_items}
/>
)
),
...props
};
}
@@ -326,31 +363,20 @@ export function ProjectCodeColumn(props: TableColumnProps): TableColumn {
};
}
export function StatusColumn({
model,
sortable,
switchable,
ordering,
accessor,
title,
hidden
}: {
export type StatusColumnProps = TableColumnProps & {
model: ModelType;
sortable?: boolean;
switchable?: boolean;
accessor?: string;
ordering?: string;
hidden?: boolean;
title?: string;
}) {
};
export function StatusColumn(props: StatusColumnProps): TableColumn {
const accessor: string = props.accessor ?? 'status';
return {
accessor: accessor ?? 'status',
sortable: sortable ?? true,
switchable: switchable ?? true,
ordering: ordering,
title: title,
hidden: hidden,
render: TableStatusRenderer(model, accessor ?? 'status_custom_key')
accessor: 'status',
sortable: true,
switchable: true,
minWidth: '50px',
render: TableStatusRenderer(props.model, accessor ?? 'status_custom_key'),
...props
};
}

View File

@@ -260,7 +260,12 @@ export function InvenTreeTable<T extends Record<string, any>>({
...col,
hidden: hidden,
resizable: col.resizable ?? true,
title: col.title ?? fieldNames[col.accessor] ?? `${col.accessor}`
title: col.title ?? fieldNames[col.accessor] ?? `${col.accessor}`,
titleStyle: (record: any, index: number) => {
return {
minWidth: (col as any).minWidth ?? '100px'
};
}
};
});

View File

@@ -30,13 +30,10 @@ export function UsedInTable({
const tableColumns: TableColumn[] = useMemo(() => {
return [
{
accessor: 'part',
switchable: false,
sortable: true,
PartColumn({
title: t`Assembly`,
render: (record: any) => PartColumn({ part: record.part_detail })
},
part: 'part_detail'
}),
{
accessor: 'part_detail.IPN',
sortable: false,
@@ -51,12 +48,12 @@ export function UsedInTable({
DescriptionColumn({
accessor: 'part_detail.description'
}),
{
PartColumn({
accessor: 'sub_part',
sortable: true,
title: t`Component`,
render: (record: any) => PartColumn({ part: record.sub_part_detail })
},
part: 'sub_part_detail'
}),
{
accessor: 'quantity',
switchable: false,

View File

@@ -97,14 +97,10 @@ export default function BuildAllocatedStockTable({
title: t`Order Status`,
hidden: showBuildInfo != true
}),
{
accessor: 'part',
PartColumn({
hidden: !showPartInfo,
title: t`Part`,
sortable: true,
switchable: false,
render: (record: any) => PartColumn({ part: record.part_detail })
},
switchable: false
}),
{
accessor: 'part_detail.IPN',
ordering: 'IPN',
@@ -136,11 +132,11 @@ export default function BuildAllocatedStockTable({
},
DecimalColumn({
accessor: 'available',
title: t`Available Quantity`
title: t`Available`
}),
DecimalColumn({
accessor: 'quantity',
title: t`Allocated Quantity`,
title: t`Allocated`,
sortable: true,
switchable: false
}),

View File

@@ -46,7 +46,8 @@ import {
DecimalColumn,
DescriptionColumn,
LocationColumn,
PartColumn
PartColumn,
RenderPartColumn
} from '../ColumnRenderers';
import { InvenTreeTable } from '../InvenTreeTable';
import RowExpansionIcon from '../RowExpansionIcon';
@@ -75,13 +76,9 @@ export function BuildLineSubTable({
const tableColumns: any[] = useMemo(() => {
return [
{
accessor: 'part',
title: t`Part`,
render: (record: any) => {
return <PartColumn part={record.part_detail} />;
}
},
PartColumn({
part: 'part_detail'
}),
{
accessor: 'quantity',
title: t`Quantity`,
@@ -305,9 +302,9 @@ export default function BuildLineTable({
const tableColumns: TableColumn[] = useMemo(() => {
return [
{
PartColumn({
accessor: 'bom_item',
title: t`Component`,
part: 'part_detail',
ordering: 'part',
sortable: true,
switchable: false,
@@ -320,11 +317,11 @@ export default function BuildLineTable({
enabled={hasAllocatedItems}
expanded={table.isRowExpanded(record.pk)}
/>
<PartColumn part={record.part_detail} />
<RenderPartColumn part={record.part_detail} />
</Group>
);
}
},
}),
{
accessor: 'part_detail.IPN',
sortable: false,
@@ -471,6 +468,7 @@ export default function BuildLineTable({
switchable: false,
sortable: true,
hidden: !isActive,
minWidth: 125,
render: (record: any) => {
if (record?.bom_item_detail?.consumable) {
return (
@@ -514,6 +512,7 @@ export default function BuildLineTable({
accessor: 'consumed',
sortable: true,
hidden: !!output?.pk,
minWidth: 125,
render: (record: any) => {
return record?.bom_item_detail?.consumable ? (
<Text style={{ fontStyle: 'italic' }}>{t`Consumable item`}</Text>

View File

@@ -68,12 +68,9 @@ export function BuildOrderTable({
const tableColumns = useMemo(() => {
return [
ReferenceColumn({}),
{
accessor: 'part',
sortable: true,
switchable: false,
render: (record: any) => PartColumn({ part: record.part_detail })
},
PartColumn({
switchable: false
}),
{
accessor: 'part_detail.IPN',
sortable: true,
@@ -92,6 +89,8 @@ export function BuildOrderTable({
},
{
accessor: 'completed',
title: t`Completed`,
minWidth: 125,
sortable: true,
switchable: false,
render: (record: any) => (

View File

@@ -56,7 +56,12 @@ import useStatusCodes from '../../hooks/UseStatusCodes';
import { useStockAdjustActions } from '../../hooks/UseStockAdjustActions';
import { useTable } from '../../hooks/UseTable';
import { useUserState } from '../../states/UserState';
import { LocationColumn, PartColumn, StatusColumn } from '../ColumnRenderers';
import {
LocationColumn,
PartColumn,
RenderPartColumn,
StatusColumn
} from '../ColumnRenderers';
import {
BatchFilter,
HasBatchCodeFilter,
@@ -100,7 +105,7 @@ function OutputAllocationDrawer({
<Space h='lg' />
<Paper withBorder p='sm'>
<Group gap='xs'>
<PartColumn part={build.part_detail} />
<RenderPartColumn part={build.part_detail} />
{output?.serial && (
<Text size='sm'>
{t`Serial Number`}: {output.serial}
@@ -578,11 +583,7 @@ export default function BuildOutputTable({
const tableColumns: TableColumn[] = useMemo(() => {
return [
{
accessor: 'part',
sortable: true,
render: (record: any) => PartColumn({ part: record?.part_detail })
},
PartColumn({}),
{
accessor: 'quantity',
ordering: 'stock',

View File

@@ -44,6 +44,7 @@ export function CompanyTable({
return [
{
accessor: 'name',
title: t`Company`,
sortable: true,
switchable: false,
render: (record: any) => {

View File

@@ -365,14 +365,10 @@ export default function ParametricPartTable({
const tableColumns: TableColumn[] = useMemo(() => {
const partColumns: TableColumn[] = [
{
accessor: 'name',
title: t`Part`,
sortable: true,
switchable: false,
noWrap: true,
render: (record: any) => PartColumn({ part: record })
},
PartColumn({
part: '',
switchable: false
}),
DescriptionColumn({
defaultVisible: false
}),

View File

@@ -55,22 +55,18 @@ export default function PartBuildAllocationsTable({
</Group>
)
},
{
accessor: 'assembly_detail',
title: t`Assembly`,
switchable: false,
render: (record: any) => <PartColumn part={record.assembly_detail} />
},
PartColumn({
part: 'assembly_detail',
title: t`Assembly`
}),
{
accessor: 'assembly_detail.IPN',
title: t`Assembly IPN`
},
{
accessor: 'part_detail',
title: t`Part`,
defaultVisible: false,
render: (record: any) => <PartColumn part={record.part_detail} />
},
PartColumn({
part: 'part_detail',
defaultVisible: false
}),
{
accessor: 'part_detail.IPN',
defaultVisible: false,

View File

@@ -52,11 +52,9 @@ export function PartParameterTable({
const tableColumns: TableColumn[] = useMemo(() => {
return [
{
accessor: 'part',
sortable: true,
render: (record: any) => PartColumn({ part: record?.part_detail })
},
PartColumn({
part: 'part_detail'
}),
{
accessor: 'part_detail.IPN',
sortable: false,

View File

@@ -53,11 +53,9 @@ export default function PartSalesAllocationsTable({
DescriptionColumn({
accessor: 'order_detail.description'
}),
{
accessor: 'part_detail',
title: t`Part`,
render: (record: any) => <PartColumn part={record.part_detail} />
},
PartColumn({
part: 'part_detail'
}),
{
accessor: 'part_detail.IPN',
title: t`IPN`

View File

@@ -39,14 +39,10 @@ import { TableHoverCard } from '../TableHoverCard';
*/
function partTableColumns(): TableColumn[] {
return [
{
accessor: 'name',
title: t`Part`,
sortable: true,
noWrap: true,
switchable: false,
render: (record: any) => PartColumn({ part: record })
},
PartColumn({
part: '',
accessor: 'name'
}),
{
accessor: 'IPN',
sortable: true

View File

@@ -41,12 +41,9 @@ export function ManufacturerPartTable({
// Construct table columns for this table
const tableColumns: TableColumn[] = useMemo(() => {
return [
{
accessor: 'part',
switchable: 'part' in params,
sortable: true,
render: (record: any) => PartColumn({ part: record?.part_detail })
},
PartColumn({
switchable: 'part' in params
}),
{
accessor: 'manufacturer',
sortable: true,
@@ -56,7 +53,7 @@ export function ManufacturerPartTable({
},
{
accessor: 'MPN',
title: t`Manufacturer Part Number`,
title: t`MPN`,
sortable: true
},
DescriptionColumn({}),

View File

@@ -132,14 +132,10 @@ export function PurchaseOrderLineItemTable({
const tableColumns: TableColumn[] = useMemo(() => {
return [
{
accessor: 'part',
title: t`Part`,
sortable: true,
ordering: 'part_name',
switchable: false,
render: (record: any) => PartColumn({ part: record.part_detail })
},
PartColumn({
part: 'part_detail',
ordering: 'part_name'
}),
{
accessor: 'part_detail.IPN',
sortable: true,

View File

@@ -115,7 +115,7 @@ export function PurchaseOrderTable({
{
accessor: 'supplier_reference'
},
LineItemsProgressColumn(),
LineItemsProgressColumn({}),
StatusColumn({ model: ModelType.purchaseorder }),
ProjectCodeColumn({
defaultVisible: false

View File

@@ -48,13 +48,10 @@ export function SupplierPartTable({
// Construct table columns for this table
const tableColumns: TableColumn[] = useMemo(() => {
return [
{
accessor: 'part',
PartColumn({
switchable: 'part' in params,
sortable: true,
render: (record: any) =>
PartColumn({ part: record?.part_detail, full_name: true })
},
part: 'part_detail'
}),
{
accessor: 'supplier',
sortable: true,

View File

@@ -106,12 +106,9 @@ export default function ReturnOrderLineItemTable({
const tableColumns: TableColumn[] = useMemo(() => {
return [
{
accessor: 'part',
title: t`Part`,
switchable: false,
render: (record: any) => PartColumn({ part: record?.part_detail })
},
PartColumn({
part: 'part_detail'
}),
{
accessor: 'part_detail.IPN',
sortable: false

View File

@@ -115,7 +115,7 @@ export function ReturnOrderTable({
accessor: 'customer_reference'
},
DescriptionColumn({}),
LineItemsProgressColumn(),
LineItemsProgressColumn({}),
StatusColumn({ model: ModelType.returnorder }),
ProjectCodeColumn({
defaultVisible: false

View File

@@ -119,14 +119,10 @@ export default function SalesOrderAllocationTable({
title: t`Order Status`,
hidden: showOrderInfo != true
}),
{
accessor: 'part',
PartColumn({
hidden: showPartInfo != true,
title: t`Part`,
sortable: true,
switchable: false,
render: (record: any) => PartColumn({ part: record.part_detail })
},
part: 'part_detail'
}),
DescriptionColumn({
accessor: 'part_detail.description',
hidden: showPartInfo != true

View File

@@ -48,7 +48,7 @@ import {
DecimalColumn,
DescriptionColumn,
LinkColumn,
PartColumn
RenderPartColumn
} from '../ColumnRenderers';
import { InvenTreeTable } from '../InvenTreeTable';
import RowExpansionIcon from '../RowExpansionIcon';
@@ -78,6 +78,7 @@ export default function SalesOrderLineItemTable({
accessor: 'part',
sortable: true,
switchable: false,
minWidth: 175,
render: (record: any) => {
return (
<Group wrap='nowrap'>
@@ -85,7 +86,7 @@ export default function SalesOrderLineItemTable({
enabled={record.allocated}
expanded={table.isRowExpanded(record.pk)}
/>
<PartColumn part={record.part_detail} />
<RenderPartColumn part={record.part_detail} />
</Group>
);
}

View File

@@ -141,10 +141,11 @@ export function SalesOrderTable({
title: t`Customer Reference`
},
DescriptionColumn({}),
LineItemsProgressColumn(),
LineItemsProgressColumn({}),
{
accessor: 'shipments_count',
title: t`Shipments`,
minWidth: 125,
render: (record: any) => (
<ProgressBar
progressLabel

View File

@@ -58,11 +58,9 @@ export default function InstalledItemsTable({
const tableColumns: TableColumn[] = useMemo(() => {
return [
{
accessor: 'part',
switchable: false,
render: (record: any) => PartColumn({ part: record?.part_detail })
},
PartColumn({
part: 'part'
}),
{
accessor: 'quantity',
switchable: false,

View File

@@ -61,11 +61,10 @@ function stockItemTableColumns({
showPricing: boolean;
}): TableColumn[] {
return [
{
PartColumn({
accessor: 'part',
sortable: true,
render: (record: any) => PartColumn({ part: record?.part_detail })
},
part: 'part_detail'
}),
{
accessor: 'part_detail.IPN',
title: t`IPN`,