diff --git a/src/frontend/src/components/details/DetailsBadge.tsx b/src/frontend/src/components/details/DetailsBadge.tsx
new file mode 100644
index 0000000000..d5228e9fd4
--- /dev/null
+++ b/src/frontend/src/components/details/DetailsBadge.tsx
@@ -0,0 +1,26 @@
+import { Badge } from '@mantine/core';
+
+export type DetailsBadgeProps = {
+ color: string;
+ label: string;
+ size?: string;
+ visible?: boolean;
+ key?: any;
+};
+
+export default function DetailsBadge(props: DetailsBadgeProps) {
+ if (props.visible == false) {
+ return null;
+ }
+
+ return (
+
+ {props.label}
+
+ );
+}
diff --git a/src/frontend/src/components/nav/PageDetail.tsx b/src/frontend/src/components/nav/PageDetail.tsx
index efaf1f3b33..6b640253f5 100644
--- a/src/frontend/src/components/nav/PageDetail.tsx
+++ b/src/frontend/src/components/nav/PageDetail.tsx
@@ -1,6 +1,7 @@
import { Group, Paper, Space, Stack, Text } from '@mantine/core';
import { Fragment, ReactNode } from 'react';
+import DetailsBadge, { DetailsBadgeProps } from '../details/DetailsBadge';
import { ApiImage } from '../images/ApiImage';
import { StylishText } from '../items/StylishText';
import { Breadcrumb, BreadcrumbList } from './BreadcrumbList';
@@ -15,6 +16,7 @@ export function PageDetail({
title,
subtitle,
detail,
+ badges,
imageUrl,
breadcrumbs,
breadcrumbAction,
@@ -24,6 +26,7 @@ export function PageDetail({
subtitle?: string;
imageUrl?: string;
detail?: ReactNode;
+ badges?: ReactNode[];
breadcrumbs?: Breadcrumb[];
breadcrumbAction?: () => void;
actions?: ReactNode[];
@@ -56,6 +59,9 @@ export function PageDetail({
{detail}
+
+ {badges}
+
{actions && (
diff --git a/src/frontend/src/pages/build/BuildDetail.tsx b/src/frontend/src/pages/build/BuildDetail.tsx
index 58e36b61a1..52695ff463 100644
--- a/src/frontend/src/pages/build/BuildDetail.tsx
+++ b/src/frontend/src/pages/build/BuildDetail.tsx
@@ -334,16 +334,17 @@ export default function BuildDetail() {
];
}, [id, build, user]);
- const buildDetail = useMemo(() => {
- return build?.status ? (
- StatusRenderer({
- status: build.status,
- type: ModelType.build
- })
- ) : (
-
- );
- }, [build, id]);
+ const buildBadges = useMemo(() => {
+ return instanceQuery.isFetching
+ ? []
+ : [
+
+ ];
+ }, [build, instanceQuery]);
return (
<>
@@ -353,7 +354,7 @@ export default function BuildDetail() {
{
- return (
-
-
- Stock: {part.in_stock}
-
-
- );
- }, [part, id]);
+ const badges: ReactNode[] = useMemo(() => {
+ if (instanceQuery.isLoading || instanceQuery.isFetching) {
+ return [];
+ }
+
+ return [
+ = part.minimum_stock ? 'green' : 'orange'}
+ visible={part.in_stock > 0}
+ />,
+ ,
+ 0}
+ />,
+ 0}
+ />
+ ];
+ }, [part, instanceQuery]);
const partFields = usePartFields({ create: false });
@@ -740,7 +763,7 @@ export default function PartDetail() {
title={t`Part` + ': ' + part.full_name}
subtitle={part.description}
imageUrl={part.image}
- detail={partDetail}
+ badges={badges}
breadcrumbs={breadcrumbs}
breadcrumbAction={() => {
setTreeOpen(true);
diff --git a/src/frontend/src/pages/purchasing/PurchaseOrderDetail.tsx b/src/frontend/src/pages/purchasing/PurchaseOrderDetail.tsx
index 74a4fb40aa..ecf43cd7bb 100644
--- a/src/frontend/src/pages/purchasing/PurchaseOrderDetail.tsx
+++ b/src/frontend/src/pages/purchasing/PurchaseOrderDetail.tsx
@@ -1,5 +1,5 @@
import { t } from '@lingui/macro';
-import { Grid, LoadingOverlay, Skeleton, Stack } from '@mantine/core';
+import { Grid, Group, LoadingOverlay, Skeleton, Stack } from '@mantine/core';
import {
IconDots,
IconInfoCircle,
@@ -8,7 +8,7 @@ import {
IconPackages,
IconPaperclip
} from '@tabler/icons-react';
-import { useMemo } from 'react';
+import { ReactNode, useMemo } from 'react';
import { useParams } from 'react-router-dom';
import { DetailsField, DetailsTable } from '../../components/details/Details';
@@ -25,6 +25,7 @@ import {
} from '../../components/items/ActionDropdown';
import { PageDetail } from '../../components/nav/PageDetail';
import { PanelGroup, PanelType } from '../../components/nav/PanelGroup';
+import { StatusRenderer } from '../../components/render/StatusRenderer';
import { NotesEditor } from '../../components/widgets/MarkdownEditor';
import { ApiEndpoints } from '../../enums/ApiEndpoints';
import { ModelType } from '../../enums/ModelType';
@@ -297,6 +298,18 @@ export default function PurchaseOrderDetail() {
];
}, [id, order, user]);
+ const orderBadges: ReactNode[] = useMemo(() => {
+ return instanceQuery.isLoading
+ ? []
+ : [
+
+ ];
+ }, [order, instanceQuery]);
+
return (
<>
{editPurchaseOrder.modal}
@@ -308,6 +321,7 @@ export default function PurchaseOrderDetail() {
imageUrl={order.supplier_detail?.image}
breadcrumbs={[{ name: t`Purchasing`, url: '/purchasing/' }]}
actions={poActions}
+ badges={orderBadges}
/>
diff --git a/src/frontend/src/pages/sales/ReturnOrderDetail.tsx b/src/frontend/src/pages/sales/ReturnOrderDetail.tsx
index 4d951172bc..2455488673 100644
--- a/src/frontend/src/pages/sales/ReturnOrderDetail.tsx
+++ b/src/frontend/src/pages/sales/ReturnOrderDetail.tsx
@@ -6,7 +6,7 @@ import {
IconNotes,
IconPaperclip
} from '@tabler/icons-react';
-import { useMemo } from 'react';
+import { ReactNode, useMemo } from 'react';
import { useParams } from 'react-router-dom';
import { DetailsField, DetailsTable } from '../../components/details/Details';
@@ -14,6 +14,7 @@ import { DetailsImage } from '../../components/details/DetailsImage';
import { ItemDetailsGrid } from '../../components/details/ItemDetails';
import { PageDetail } from '../../components/nav/PageDetail';
import { PanelGroup, PanelType } from '../../components/nav/PanelGroup';
+import { StatusRenderer } from '../../components/render/StatusRenderer';
import { NotesEditor } from '../../components/widgets/MarkdownEditor';
import { ApiEndpoints } from '../../enums/ApiEndpoints';
import { ModelType } from '../../enums/ModelType';
@@ -220,6 +221,18 @@ export default function ReturnOrderDetail() {
];
}, [order, id]);
+ const orderBadges: ReactNode[] = useMemo(() => {
+ return instanceQuery.isLoading
+ ? []
+ : [
+
+ ];
+ }, [order, instanceQuery]);
+
return (
<>
@@ -228,6 +241,7 @@ export default function ReturnOrderDetail() {
title={t`Return Order` + `: ${order.reference}`}
subtitle={order.description}
imageUrl={order.customer_detail?.image}
+ badges={orderBadges}
breadcrumbs={[{ name: t`Sales`, url: '/sales/' }]}
/>
diff --git a/src/frontend/src/pages/sales/SalesOrderDetail.tsx b/src/frontend/src/pages/sales/SalesOrderDetail.tsx
index aecb19c42d..182c0fbfde 100644
--- a/src/frontend/src/pages/sales/SalesOrderDetail.tsx
+++ b/src/frontend/src/pages/sales/SalesOrderDetail.tsx
@@ -9,7 +9,7 @@ import {
IconTruckDelivery,
IconTruckLoading
} from '@tabler/icons-react';
-import { useMemo } from 'react';
+import { ReactNode, useMemo } from 'react';
import { useParams } from 'react-router-dom';
import { DetailsField, DetailsTable } from '../../components/details/Details';
@@ -17,6 +17,7 @@ import { DetailsImage } from '../../components/details/DetailsImage';
import { ItemDetailsGrid } from '../../components/details/ItemDetails';
import { PageDetail } from '../../components/nav/PageDetail';
import { PanelGroup, PanelType } from '../../components/nav/PanelGroup';
+import { StatusRenderer } from '../../components/render/StatusRenderer';
import { NotesEditor } from '../../components/widgets/MarkdownEditor';
import { ApiEndpoints } from '../../enums/ApiEndpoints';
import { ModelType } from '../../enums/ModelType';
@@ -244,6 +245,18 @@ export default function SalesOrderDetail() {
];
}, [order, id]);
+ const orderBadges: ReactNode[] = useMemo(() => {
+ return instanceQuery.isLoading
+ ? []
+ : [
+
+ ];
+ }, [order, instanceQuery]);
+
return (
<>
@@ -252,6 +265,7 @@ export default function SalesOrderDetail() {
title={t`Sales Order` + `: ${order.reference}`}
subtitle={order.description}
imageUrl={order.customer_detail?.image}
+ badges={orderBadges}
breadcrumbs={[{ name: t`Sales`, url: '/sales/' }]}
/>
diff --git a/src/frontend/src/pages/stock/StockDetail.tsx b/src/frontend/src/pages/stock/StockDetail.tsx
index 3806d5c17b..072b881823 100644
--- a/src/frontend/src/pages/stock/StockDetail.tsx
+++ b/src/frontend/src/pages/stock/StockDetail.tsx
@@ -1,7 +1,9 @@
import { t } from '@lingui/macro';
import {
Alert,
+ Badge,
Grid,
+ Group,
LoadingOverlay,
Skeleton,
Stack,
@@ -20,10 +22,11 @@ import {
IconPaperclip,
IconSitemap
} from '@tabler/icons-react';
-import { useMemo, useState } from 'react';
+import { ReactNode, useMemo, useState } from 'react';
import { useParams } from 'react-router-dom';
import { DetailsField, DetailsTable } from '../../components/details/Details';
+import DetailsBadge from '../../components/details/DetailsBadge';
import { DetailsImage } from '../../components/details/DetailsImage';
import { ItemDetailsGrid } from '../../components/details/ItemDetails';
import {
@@ -38,6 +41,7 @@ import {
import { PageDetail } from '../../components/nav/PageDetail';
import { PanelGroup, PanelType } from '../../components/nav/PanelGroup';
import { StockLocationTree } from '../../components/nav/StockLocationTree';
+import { StatusRenderer } from '../../components/render/StatusRenderer';
import { NotesEditor } from '../../components/widgets/MarkdownEditor';
import { ApiEndpoints } from '../../enums/ApiEndpoints';
import { ModelType } from '../../enums/ModelType';
@@ -437,6 +441,33 @@ export default function StockDetail() {
[id, stockitem, user]
);
+ const stockBadges: ReactNode[] = useMemo(() => {
+ return instanceQuery.isLoading
+ ? []
+ : [
+ ,
+ ,
+ ,
+
+ ];
+ }, [stockitem, instanceQuery]);
+
return (
@@ -449,11 +480,7 @@ export default function StockDetail() {
title={t`Stock Item`}
subtitle={stockitem.part_detail?.full_name}
imageUrl={stockitem.part_detail?.thumbnail}
- detail={
-
- Quantity: {stockitem.quantity ?? 'idk'}
-
- }
+ badges={stockBadges}
breadcrumbs={breadcrumbs}
breadcrumbAction={() => {
setTreeOpen(true);
diff --git a/src/frontend/src/tables/InvenTreeTable.tsx b/src/frontend/src/tables/InvenTreeTable.tsx
index f27299bc82..6de23f03bc 100644
--- a/src/frontend/src/tables/InvenTreeTable.tsx
+++ b/src/frontend/src/tables/InvenTreeTable.tsx
@@ -91,6 +91,7 @@ export type InvenTreeTableProps = {
onRowClick?: (record: T, index: number, event: any) => void;
onCellClick?: DataTableCellClickHandler;
modelType?: ModelType;
+ modelField?: string;
};
/**
@@ -515,18 +516,22 @@ export function InvenTreeTable({
if (props.onRowClick) {
// If a custom row click handler is provided, use that
props.onRowClick(record, index, event);
- } else if (tableProps.modelType && record?.pk) {
- // If a model type is provided, navigate to the detail view for that model
- let url = getDetailUrl(tableProps.modelType, record.pk);
+ } else if (tableProps.modelType) {
+ const pk = record?.[tableProps.modelField ?? 'pk'];
- // Should it be opened in a new tab?
- if (event?.ctrlKey || event?.shiftKey) {
- // Open in a new tab
- url = `/${base_url}${url}`;
- window.open(url, '_blank');
- } else {
- // Navigate internally
- navigate(url);
+ if (pk) {
+ // If a model type is provided, navigate to the detail view for that model
+ let url = getDetailUrl(tableProps.modelType, pk);
+
+ // Should it be opened in a new tab?
+ if (event?.ctrlKey || event?.shiftKey) {
+ // Open in a new tab
+ url = `/${base_url}${url}`;
+ window.open(url, '_blank');
+ } else {
+ // Navigate internally
+ navigate(url);
+ }
}
}
},
diff --git a/src/frontend/src/tables/bom/UsedInTable.tsx b/src/frontend/src/tables/bom/UsedInTable.tsx
index 1033e0effb..6f62a7ef31 100644
--- a/src/frontend/src/tables/bom/UsedInTable.tsx
+++ b/src/frontend/src/tables/bom/UsedInTable.tsx
@@ -83,7 +83,8 @@ export function UsedInTable({
sub_part_detail: true
},
tableFilters: tableFilters,
- modelType: ModelType.part
+ modelType: ModelType.part,
+ modelField: 'part'
}}
/>
);
diff --git a/src/frontend/src/tables/part/ParametricPartTable.tsx b/src/frontend/src/tables/part/ParametricPartTable.tsx
index ee2baa620f..80a41bdcfe 100644
--- a/src/frontend/src/tables/part/ParametricPartTable.tsx
+++ b/src/frontend/src/tables/part/ParametricPartTable.tsx
@@ -234,6 +234,10 @@ export default function ParametricPartTable({
{
accessor: 'IPN',
sortable: true
+ },
+ {
+ accessor: 'total_in_stock',
+ sortable: true
}
];