mirror of
https://github.com/inventree/InvenTree.git
synced 2025-04-28 11:36:44 +00:00
* Add new hook - useTable - Will replace useTableRefresh - More extensible (further functionality to follow) - Pass entire state through to final table - Defined interface for return type * Update BomTable * Update UsedInTable * Refactor attachment table * Update remaining tables * Clean StockItemTable * Fix NotificationTable * Remove unused import
292 lines
7.2 KiB
TypeScript
292 lines
7.2 KiB
TypeScript
import { t } from '@lingui/macro';
|
|
import { Group, Text } from '@mantine/core';
|
|
import { ReactNode, useMemo } from 'react';
|
|
import { useNavigate } from 'react-router-dom';
|
|
|
|
import { ApiPaths } from '../../../enums/ApiEndpoints';
|
|
import { shortenString } from '../../../functions/tables';
|
|
import { useTable } from '../../../hooks/UseTable';
|
|
import { apiUrl } from '../../../states/ApiState';
|
|
import { Thumbnail } from '../../images/Thumbnail';
|
|
import { TableColumn } from '../Column';
|
|
import { DescriptionColumn, LinkColumn } from '../ColumnRenderers';
|
|
import { TableFilter } from '../Filter';
|
|
import { InvenTreeTable, InvenTreeTableProps } from '../InvenTreeTable';
|
|
import { TableHoverCard } from '../TableHoverCard';
|
|
|
|
/**
|
|
* Construct a list of columns for the part table
|
|
*/
|
|
function partTableColumns(): TableColumn[] {
|
|
return [
|
|
{
|
|
accessor: 'name',
|
|
sortable: true,
|
|
noWrap: true,
|
|
title: t`Part`,
|
|
render: function (record: any) {
|
|
return (
|
|
<Thumbnail
|
|
src={record.thumbnail || record.image}
|
|
alt={record.name}
|
|
text={record.full_name}
|
|
/>
|
|
);
|
|
}
|
|
},
|
|
{
|
|
accessor: 'IPN',
|
|
title: t`IPN`,
|
|
sortable: true
|
|
},
|
|
{
|
|
accessor: 'units',
|
|
sortable: true,
|
|
title: t`Units`
|
|
},
|
|
DescriptionColumn(),
|
|
{
|
|
accessor: 'category',
|
|
title: t`Category`,
|
|
sortable: true,
|
|
|
|
render: function (record: any) {
|
|
// TODO: Link to the category detail page
|
|
return shortenString({
|
|
str: record.category_detail?.pathstring
|
|
});
|
|
}
|
|
},
|
|
{
|
|
accessor: 'total_in_stock',
|
|
title: t`Stock`,
|
|
sortable: true,
|
|
|
|
render: (record) => {
|
|
let extra: ReactNode[] = [];
|
|
|
|
let stock = record?.total_in_stock ?? 0;
|
|
let allocated =
|
|
(record?.allocated_to_build_orders ?? 0) +
|
|
(record?.allocated_to_sales_orders ?? 0);
|
|
let available = Math.max(0, stock - allocated);
|
|
let min_stock = record?.minimum_stock ?? 0;
|
|
|
|
let text = String(stock);
|
|
|
|
let color: string | undefined = undefined;
|
|
|
|
if (min_stock > stock) {
|
|
extra.push(
|
|
<Text key="min-stock" color="orange">
|
|
{t`Minimum stock` + `: ${min_stock}`}
|
|
</Text>
|
|
);
|
|
|
|
color = 'orange';
|
|
}
|
|
|
|
if (record.ordering > 0) {
|
|
extra.push(
|
|
<Text key="on-order">{t`On Order` + `: ${record.ordering}`}</Text>
|
|
);
|
|
}
|
|
|
|
if (record.building) {
|
|
extra.push(
|
|
<Text key="building">{t`Building` + `: ${record.building}`}</Text>
|
|
);
|
|
}
|
|
|
|
if (record.allocated_to_build_orders > 0) {
|
|
extra.push(
|
|
<Text key="bo-allocations">
|
|
{t`Build Order Allocations` +
|
|
`: ${record.allocated_to_build_orders}`}
|
|
</Text>
|
|
);
|
|
}
|
|
|
|
if (record.allocated_to_sales_orders > 0) {
|
|
extra.push(
|
|
<Text key="so-allocations">
|
|
{t`Sales Order Allocations` +
|
|
`: ${record.allocated_to_sales_orders}`}
|
|
</Text>
|
|
);
|
|
}
|
|
|
|
if (available != stock) {
|
|
extra.push(
|
|
<Text key="available">{t`Available` + `: ${available}`}</Text>
|
|
);
|
|
}
|
|
|
|
// TODO: Add extra information on stock "demand"
|
|
|
|
if (stock <= 0) {
|
|
color = 'red';
|
|
text = t`No stock`;
|
|
} else if (available <= 0) {
|
|
color = 'orange';
|
|
} else if (available < min_stock) {
|
|
color = 'yellow';
|
|
}
|
|
|
|
return (
|
|
<TableHoverCard
|
|
value={
|
|
<Group spacing="xs" position="left">
|
|
<Text color={color}>{text}</Text>
|
|
{record.units && (
|
|
<Text size="xs" color={color}>
|
|
[{record.units}]
|
|
</Text>
|
|
)}
|
|
</Group>
|
|
}
|
|
title={t`Stock Information`}
|
|
extra={extra}
|
|
/>
|
|
);
|
|
}
|
|
},
|
|
{
|
|
accessor: 'price_range',
|
|
title: t`Price Range`,
|
|
sortable: false,
|
|
|
|
render: function (record: any) {
|
|
// TODO: Render price range
|
|
return '-- price --';
|
|
}
|
|
},
|
|
LinkColumn()
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Construct a set of filters for the part table
|
|
*/
|
|
function partTableFilters(): TableFilter[] {
|
|
return [
|
|
{
|
|
name: 'active',
|
|
label: t`Active`,
|
|
description: t`Filter by part active status`,
|
|
type: 'boolean'
|
|
},
|
|
{
|
|
name: 'assembly',
|
|
label: t`Assembly`,
|
|
description: t`Filter by assembly attribute`,
|
|
type: 'boolean'
|
|
},
|
|
{
|
|
name: 'cascade',
|
|
label: t`Include Subcategories`,
|
|
description: t`Include parts in subcategories`,
|
|
type: 'boolean'
|
|
},
|
|
{
|
|
name: 'component',
|
|
label: t`Component`,
|
|
description: t`Filter by component attribute`,
|
|
type: 'boolean'
|
|
},
|
|
{
|
|
name: 'trackable',
|
|
label: t`Trackable`,
|
|
description: t`Filter by trackable attribute`,
|
|
type: 'boolean'
|
|
},
|
|
{
|
|
name: 'has_units',
|
|
label: t`Has Units`,
|
|
description: t`Filter by parts which have units`,
|
|
type: 'boolean'
|
|
},
|
|
{
|
|
name: 'has_ipn',
|
|
label: t`Has IPN`,
|
|
description: t`Filter by parts which have an internal part number`,
|
|
type: 'boolean'
|
|
},
|
|
{
|
|
name: 'has_stock',
|
|
label: t`Has Stock`,
|
|
description: t`Filter by parts which have stock`,
|
|
type: 'boolean'
|
|
},
|
|
{
|
|
name: 'low_stock',
|
|
label: t`Low Stock`,
|
|
description: t`Filter by parts which have low stock`,
|
|
type: 'boolean'
|
|
},
|
|
{
|
|
name: 'purchaseable',
|
|
label: t`Purchaseable`,
|
|
description: t`Filter by parts which are purchaseable`,
|
|
type: 'boolean'
|
|
},
|
|
{
|
|
name: 'salable',
|
|
label: t`Salable`,
|
|
description: t`Filter by parts which are salable`,
|
|
type: 'boolean'
|
|
},
|
|
{
|
|
name: 'virtual',
|
|
label: t`Virtual`,
|
|
description: t`Filter by parts which are virtual`,
|
|
type: 'choice',
|
|
choices: [
|
|
{ value: 'true', label: t`Virtual` },
|
|
{ value: 'false', label: t`Not Virtual` }
|
|
]
|
|
}
|
|
// unallocated_stock
|
|
// starred
|
|
// stocktake
|
|
// is_template
|
|
// virtual
|
|
// has_pricing
|
|
// TODO: Any others from table_filters.js?
|
|
];
|
|
}
|
|
|
|
/**
|
|
* PartListTable - Displays a list of parts, based on the provided parameters
|
|
* @param {Object} params - The query parameters to pass to the API
|
|
* @returns
|
|
*/
|
|
export function PartListTable({ props }: { props: InvenTreeTableProps }) {
|
|
const tableColumns = useMemo(() => partTableColumns(), []);
|
|
const tableFilters = useMemo(() => partTableFilters(), []);
|
|
|
|
const table = useTable('part-list');
|
|
|
|
const navigate = useNavigate();
|
|
|
|
return (
|
|
<InvenTreeTable
|
|
url={apiUrl(ApiPaths.part_list)}
|
|
tableState={table}
|
|
columns={tableColumns}
|
|
props={{
|
|
...props,
|
|
enableDownload: true,
|
|
customFilters: tableFilters,
|
|
params: {
|
|
...props.params,
|
|
category_detail: true
|
|
},
|
|
onRowClick: (record, _index, _event) => {
|
|
navigate(`/part/${record.pk}/`);
|
|
}
|
|
}}
|
|
/>
|
|
);
|
|
}
|