diff --git a/InvenTree/part/serializers.py b/InvenTree/part/serializers.py index f30ba199fd..5740530960 100644 --- a/InvenTree/part/serializers.py +++ b/InvenTree/part/serializers.py @@ -715,6 +715,8 @@ class PartSerializer(InvenTree.serializers.RemoteImageMixin, InvenTree.serialize unallocated_stock = serializers.FloatField(read_only=True) variant_stock = serializers.FloatField(read_only=True) + minimum_stock = serializers.FloatField() + image = InvenTree.serializers.InvenTreeImageSerializerField(required=False, allow_null=True) thumbnail = serializers.CharField(source='get_thumbnail_url', read_only=True) starred = serializers.SerializerMethodField() diff --git a/src/frontend/src/components/tables/part/PartTable.tsx b/src/frontend/src/components/tables/part/PartTable.tsx index 14bc6ce90e..ca76d7b521 100644 --- a/src/frontend/src/components/tables/part/PartTable.tsx +++ b/src/frontend/src/components/tables/part/PartTable.tsx @@ -1,6 +1,6 @@ import { t } from '@lingui/macro'; -import { Group, Text } from '@mantine/core'; -import { useMemo } from 'react'; +import { Group, Stack, Text } from '@mantine/core'; +import { ReactNode, useMemo } from 'react'; import { useNavigate } from 'react-router-dom'; import { shortenString } from '../../../functions/tables'; @@ -10,6 +10,7 @@ import { Thumbnail } from '../../images/Thumbnail'; import { TableColumn } from '../Column'; import { TableFilter } from '../Filter'; import { InvenTreeTable, InvenTreeTableProps } from '../InvenTreeTable'; +import { TableHoverCard } from '../TableHoverCard'; /** * Construct a list of columns for the part table @@ -57,6 +58,7 @@ function partTableColumns(): TableColumn[] { accessor: 'category', title: t`Category`, sortable: true, + switchable: true, render: function (record: any) { // TODO: Link to the category detail page return shortenString({ @@ -68,7 +70,76 @@ function partTableColumns(): TableColumn[] { accessor: 'total_in_stock', title: t`Stock`, sortable: true, - switchable: true + switchable: true, + render: (record) => { + let extra: ReactNode[] = []; + + let stock = record?.total_in_stock ?? 0; + + let text = String(stock); + + let color: string | undefined = undefined; + + if (record.minimum_stock > stock) { + extra.push( + + {t`Minimum stock` + `: ${record.minimum_stock}`} + + ); + + color = 'orange'; + } + + if (record.ordering > 0) { + extra.push({t`On Order` + `: ${record.ordering}`}); + } + + if (record.building) { + extra.push({t`Building` + `: ${record.building}`}); + } + + if (record.allocated_to_build_orders > 0) { + extra.push( + + {t`Build Order Allocations` + + `: ${record.allocated_to_build_orders}`} + + ); + } + + if (record.allocated_to_sales_orders > 0) { + extra.push( + + {t`Sales Order Allocations` + + `: ${record.allocated_to_sales_orders}`} + + ); + } + + // TODO: Add extra information on stock "deman" + + if (stock == 0) { + color = 'red'; + text = t`No stock`; + } + + return ( + + {text} + {record.units && ( + + [{record.units}] + + )} + + } + title={t`Stock Information`} + extra={extra.length > 0 && {extra}} + /> + ); + } }, { accessor: 'price_range',