diff --git a/src/frontend/src/components/details/DetailsImage.tsx b/src/frontend/src/components/details/DetailsImage.tsx
index d6da80ad9c..6175af084a 100644
--- a/src/frontend/src/components/details/DetailsImage.tsx
+++ b/src/frontend/src/components/details/DetailsImage.tsx
@@ -32,7 +32,7 @@ export type DetailImageProps = {
appRole: UserRoles;
src: string;
apiPath: string;
- refresh: () => void;
+ refresh?: () => void;
imageActions?: DetailImageButtonProps;
pk: string;
};
@@ -335,7 +335,7 @@ export function DetailsImage(props: DetailImageProps) {
// Sets a new image, and triggers upstream instance refresh
const setAndRefresh = (image: string) => {
setImg(image);
- props.refresh();
+ props.refresh && props.refresh();
};
const permissions = useUserState();
diff --git a/src/frontend/src/functions/icons.tsx b/src/frontend/src/functions/icons.tsx
index e0774beed9..6bcbb2cd71 100644
--- a/src/frontend/src/functions/icons.tsx
+++ b/src/frontend/src/functions/icons.tsx
@@ -2,8 +2,10 @@ import {
Icon123,
IconBinaryTree2,
IconBookmarks,
+ IconBox,
IconBuilding,
IconBuildingFactory2,
+ IconCalendar,
IconCalendarStats,
IconCheck,
IconClipboardList,
@@ -18,14 +20,17 @@ import {
IconLink,
IconList,
IconListTree,
+ IconMapPin,
IconMapPinHeart,
IconNotes,
IconPackage,
+ IconPackageImport,
IconPackages,
IconPaperclip,
IconPhoto,
IconQuestionMark,
IconRulerMeasure,
+ IconShape,
IconShoppingCart,
IconShoppingCartHeart,
IconStack2,
@@ -99,9 +104,13 @@ const icons: { [key: string]: (props: TablerIconsProps) => React.JSX.Element } =
saleable: IconCurrencyDollar,
virtual: IconWorldCode,
inactive: IconX,
+ part: IconBox,
+ supplier_part: IconPackageImport,
+ calendar: IconCalendar,
external: IconExternalLink,
creation_date: IconCalendarTime,
+ location: IconMapPin,
default_location: IconMapPinHeart,
default_supplier: IconShoppingCartHeart,
link: IconLink,
@@ -138,3 +147,6 @@ export function InvenTreeIcon(props: IconProps) {
return ;
}
+function IconShapes(props: TablerIconsProps): Element {
+ throw new Error('Function not implemented.');
+}
diff --git a/src/frontend/src/pages/stock/StockDetail.tsx b/src/frontend/src/pages/stock/StockDetail.tsx
index 4901bf20ed..5eda863d73 100644
--- a/src/frontend/src/pages/stock/StockDetail.tsx
+++ b/src/frontend/src/pages/stock/StockDetail.tsx
@@ -1,5 +1,12 @@
import { t } from '@lingui/macro';
-import { Alert, LoadingOverlay, Skeleton, Stack, Text } from '@mantine/core';
+import {
+ Alert,
+ Grid,
+ LoadingOverlay,
+ Skeleton,
+ Stack,
+ Text
+} from '@mantine/core';
import {
IconBookmark,
IconBoxPadding,
@@ -20,6 +27,11 @@ import {
import { useMemo, useState } from 'react';
import { useParams } from 'react-router-dom';
+import { DetailsImage } from '../../components/details/DetailsImage';
+import {
+ ItemDetails,
+ ItemDetailsGrid
+} from '../../components/details/ItemDetails';
import {
ActionDropdown,
BarcodeActionDropdown,
@@ -34,10 +46,13 @@ import { PanelGroup, PanelType } from '../../components/nav/PanelGroup';
import { StockLocationTree } from '../../components/nav/StockLocationTree';
import { NotesEditor } from '../../components/widgets/MarkdownEditor';
import { ApiEndpoints } from '../../enums/ApiEndpoints';
+import { ModelType } from '../../enums/ModelType';
+import { UserRoles } from '../../enums/Roles';
import { useEditStockItem } from '../../forms/StockForms';
import { useInstance } from '../../hooks/UseInstance';
import { apiUrl } from '../../states/ApiState';
import { useUserState } from '../../states/UserState';
+import { DetailsField, DetailsTable } from '../../tables/Details';
import { AttachmentTable } from '../../tables/general/AttachmentTable';
import { StockItemTable } from '../../tables/stock/StockItemTable';
import StockItemTestResultTable from '../../tables/stock/StockItemTestResultTable';
@@ -63,12 +78,151 @@ export default function StockDetail() {
}
});
+ const detailsPanel = useMemo(() => {
+ let data = stockitem;
+
+ data.available_stock = Math.max(0, data.quantity - data.allocated);
+
+ if (instanceQuery.isFetching) {
+ return ;
+ }
+
+ // Top left - core part information
+ let tl: DetailsField[] = [
+ {
+ name: 'part',
+ label: t`Base Part`,
+ type: 'link',
+ model: ModelType.part
+ },
+ {
+ name: 'status',
+ type: 'text',
+ label: t`Stock Status`
+ },
+ {
+ type: 'text',
+ name: 'tests',
+ label: `Completed Tests`
+ },
+ {
+ type: 'text',
+ name: 'updated',
+ icon: 'calendar',
+ label: t`Last Updated`
+ },
+ {
+ type: 'text',
+ name: 'stocktake',
+ icon: 'calendar',
+ label: t`Last Stocktake`,
+ hidden: !stockitem.stocktake
+ }
+ ];
+
+ // Top right - available stock information
+ let tr: DetailsField[] = [
+ {
+ type: 'text',
+ name: 'quantity',
+ label: t`Quantity`
+ },
+ {
+ type: 'text',
+ name: 'serial',
+ label: t`Serial Number`,
+ hidden: !stockitem.serial
+ },
+ {
+ type: 'text',
+ name: 'available_stock',
+ label: t`In Stock`
+ }
+ // TODO: allocated_to_sales_orders
+ // TODO: allocated_to_build_orders
+ ];
+
+ // Bottom left: location information
+ let bl: DetailsField[] = [
+ {
+ name: 'supplier_part',
+ label: t`Supplier Part`,
+ type: 'link',
+ model: ModelType.supplierpart,
+ hidden: !stockitem.supplier_part
+ },
+ {
+ type: 'link',
+ name: 'location',
+ label: t`Location`,
+ model: ModelType.stocklocation,
+ hidden: !stockitem.location
+ },
+ {
+ type: 'link',
+ name: 'belongs_to',
+ label: t`Installed In`,
+ model: ModelType.stockitem,
+ hidden: !stockitem.belongs_to
+ },
+ {
+ type: 'link',
+ name: 'consumed_by',
+ label: t`Consumed By`,
+ model: ModelType.build,
+ hidden: !stockitem.consumed_by
+ },
+ {
+ type: 'link',
+ name: 'sales_order',
+ label: t`Sales Order`,
+ model: ModelType.salesorder,
+ hidden: !stockitem.sales_order
+ }
+ ];
+
+ // Bottom right - any other information
+ let br: DetailsField[] = [
+ // TODO: Expiry date
+ // TODO: Ownership
+ {
+ type: 'text',
+ name: 'packaging',
+ icon: 'part',
+ label: t`Packaging`,
+ hidden: !stockitem.packaging
+ }
+ ];
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+ }, [stockitem, instanceQuery]);
+
const stockPanels: PanelType[] = useMemo(() => {
return [
{
name: 'details',
label: t`Details`,
- icon:
+ icon: ,
+ content: detailsPanel
},
{
name: 'tracking',