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',