From a652ed85aaa040ff467d9fa00567b6e6b8ddd9b4 Mon Sep 17 00:00:00 2001 From: Oliver Date: Fri, 1 Mar 2024 00:31:02 +0000 Subject: [PATCH] Update stockitem details page --- .../src/components/details/DetailsImage.tsx | 4 +- src/frontend/src/functions/icons.tsx | 12 ++ src/frontend/src/pages/stock/StockDetail.tsx | 158 +++++++++++++++++- 3 files changed, 170 insertions(+), 4 deletions(-) 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',