mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-10-31 21:25:42 +00:00 
			
		
		
		
	React updates (#5826)
* Add more panels to StockItem page * Add some placeholder actions for StockItem page * edit stock item * Add info hover card to stocktable * update extra info for part table * Add extra columns to PurchaseOrder table * Fix unused import
This commit is contained in:
		| @@ -1,4 +1,6 @@ | |||||||
|  | import { t } from '@lingui/macro'; | ||||||
| import { ActionIcon, Menu, Tooltip } from '@mantine/core'; | import { ActionIcon, Menu, Tooltip } from '@mantine/core'; | ||||||
|  | import { IconQrcode } from '@tabler/icons-react'; | ||||||
| import { ReactNode, useMemo } from 'react'; | import { ReactNode, useMemo } from 'react'; | ||||||
|  |  | ||||||
| import { notYetImplemented } from '../../functions/notifications'; | import { notYetImplemented } from '../../functions/notifications'; | ||||||
| @@ -32,7 +34,7 @@ export function ActionDropdown({ | |||||||
|   return hasActions ? ( |   return hasActions ? ( | ||||||
|     <Menu position="bottom-end"> |     <Menu position="bottom-end"> | ||||||
|       <Menu.Target> |       <Menu.Target> | ||||||
|         <Tooltip label={tooltip}> |         <Tooltip label={tooltip} hidden={!tooltip}> | ||||||
|           <ActionIcon size="lg" radius="sm" variant="outline"> |           <ActionIcon size="lg" radius="sm" variant="outline"> | ||||||
|             {icon} |             {icon} | ||||||
|           </ActionIcon> |           </ActionIcon> | ||||||
| @@ -63,3 +65,19 @@ export function ActionDropdown({ | |||||||
|     </Menu> |     </Menu> | ||||||
|   ) : null; |   ) : null; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // Dropdown menu for barcode actions | ||||||
|  | export function BarcodeActionDropdown({ | ||||||
|  |   actions | ||||||
|  | }: { | ||||||
|  |   actions: ActionDropdownItem[]; | ||||||
|  | }) { | ||||||
|  |   return ( | ||||||
|  |     <ActionDropdown | ||||||
|  |       key="barcode" | ||||||
|  |       tooltip={t`Barcode Actions`} | ||||||
|  |       icon={<IconQrcode />} | ||||||
|  |       actions={actions} | ||||||
|  |     /> | ||||||
|  |   ); | ||||||
|  | } | ||||||
|   | |||||||
| @@ -75,16 +75,19 @@ function partTableColumns(): TableColumn[] { | |||||||
|         let extra: ReactNode[] = []; |         let extra: ReactNode[] = []; | ||||||
|  |  | ||||||
|         let stock = record?.total_in_stock ?? 0; |         let stock = record?.total_in_stock ?? 0; | ||||||
|  |         let allocated = | ||||||
|  |           (record?.allocated_to_build_orders ?? 0) + | ||||||
|  |           (record?.allocated_to_sales_orders ?? 0); | ||||||
|  |         let available = Math.max(0, stock - allocated); | ||||||
|  |         let min_stock = record?.minimum_stock ?? 0; | ||||||
|  |  | ||||||
|         let text = String(stock); |         let text = String(stock); | ||||||
|  |  | ||||||
|         let color: string | undefined = undefined; |         let color: string | undefined = undefined; | ||||||
|  |  | ||||||
|         if (record.minimum_stock > stock) { |         if (min_stock > stock) { | ||||||
|           extra.push( |           extra.push( | ||||||
|             <Text color="orange"> |             <Text color="orange">{t`Minimum stock` + `: ${min_stock}`}</Text> | ||||||
|               {t`Minimum stock` + `: ${record.minimum_stock}`} |  | ||||||
|             </Text> |  | ||||||
|           ); |           ); | ||||||
|  |  | ||||||
|           color = 'orange'; |           color = 'orange'; | ||||||
| @@ -116,11 +119,19 @@ function partTableColumns(): TableColumn[] { | |||||||
|           ); |           ); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         // TODO: Add extra information on stock "deman" |         if (available != stock) { | ||||||
|  |           extra.push(<Text>{t`Available` + `: ${available}`}</Text>); | ||||||
|  |         } | ||||||
|  |  | ||||||
|         if (stock == 0) { |         // TODO: Add extra information on stock "demand" | ||||||
|  |  | ||||||
|  |         if (stock <= 0) { | ||||||
|           color = 'red'; |           color = 'red'; | ||||||
|           text = t`No stock`; |           text = t`No stock`; | ||||||
|  |         } else if (available <= 0) { | ||||||
|  |           color = 'orange'; | ||||||
|  |         } else if (available < min_stock) { | ||||||
|  |           color = 'yellow'; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         return ( |         return ( | ||||||
| @@ -129,7 +140,7 @@ function partTableColumns(): TableColumn[] { | |||||||
|               <Group spacing="xs" position="left"> |               <Group spacing="xs" position="left"> | ||||||
|                 <Text color={color}>{text}</Text> |                 <Text color={color}>{text}</Text> | ||||||
|                 {record.units && ( |                 {record.units && ( | ||||||
|                   <Text size="xs" color="color"> |                   <Text size="xs" color={color}> | ||||||
|                     [{record.units}] |                     [{record.units}] | ||||||
|                   </Text> |                   </Text> | ||||||
|                 )} |                 )} | ||||||
|   | |||||||
| @@ -61,7 +61,7 @@ export function PurchaseOrderTable({ params }: { params?: any }) { | |||||||
|         accessor: 'project_code', |         accessor: 'project_code', | ||||||
|         title: t`Project Code`, |         title: t`Project Code`, | ||||||
|         switchable: true |         switchable: true | ||||||
|         // TODO: Custom formatter |         // TODO: Custom project code formatter | ||||||
|       }, |       }, | ||||||
|       { |       { | ||||||
|         accessor: 'status', |         accessor: 'status', | ||||||
| @@ -78,22 +78,34 @@ export function PurchaseOrderTable({ params }: { params?: any }) { | |||||||
|         accessor: 'creation_date', |         accessor: 'creation_date', | ||||||
|         title: t`Created`, |         title: t`Created`, | ||||||
|         switchable: true |         switchable: true | ||||||
|         // TODO: Custom formatter |         // TODO: Custom date formatter | ||||||
|       }, |       }, | ||||||
|       { |       { | ||||||
|         accessor: 'target_date', |         accessor: 'target_date', | ||||||
|         title: t`Target Date`, |         title: t`Target Date`, | ||||||
|         switchable: true |         switchable: true | ||||||
|         // TODO: Custom formatter |         // TODO: Custom date formatter | ||||||
|       }, |       }, | ||||||
|       { |       { | ||||||
|         accessor: 'line_items', |         accessor: 'line_items', | ||||||
|         title: t`Line Items`, |         title: t`Line Items`, | ||||||
|         sortable: true, |         sortable: true, | ||||||
|         switchable: true |         switchable: true | ||||||
|  |       }, | ||||||
|  |       { | ||||||
|  |         accessor: 'total_price', | ||||||
|  |         title: t`Total Price`, | ||||||
|  |         sortable: true, | ||||||
|  |         switchable: true | ||||||
|  |         // TODO: Custom money formatter | ||||||
|  |       }, | ||||||
|  |       { | ||||||
|  |         accessor: 'responsible', | ||||||
|  |         title: t`Responsible`, | ||||||
|  |         sortable: true, | ||||||
|  |         switchable: true | ||||||
|  |         // TODO: custom 'owner' formatter | ||||||
|       } |       } | ||||||
|       // TODO: total_price |  | ||||||
|       // TODO: responsible |  | ||||||
|     ]; |     ]; | ||||||
|   }, []); |   }, []); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| import { t } from '@lingui/macro'; | import { t } from '@lingui/macro'; | ||||||
| import { Group, Text } from '@mantine/core'; | import { Group, Stack, Text } from '@mantine/core'; | ||||||
| import { useMemo } from 'react'; | import { ReactNode, useMemo } from 'react'; | ||||||
| import { useNavigate } from 'react-router-dom'; | import { useNavigate } from 'react-router-dom'; | ||||||
|  |  | ||||||
| import { notYetImplemented } from '../../../functions/notifications'; | import { notYetImplemented } from '../../../functions/notifications'; | ||||||
| @@ -12,6 +12,7 @@ import { TableStatusRenderer } from '../../renderers/StatusRenderer'; | |||||||
| import { TableColumn } from '../Column'; | import { TableColumn } from '../Column'; | ||||||
| import { TableFilter } from '../Filter'; | import { TableFilter } from '../Filter'; | ||||||
| import { RowAction } from '../RowActions'; | import { RowAction } from '../RowActions'; | ||||||
|  | import { TableHoverCard } from '../TableHoverCard'; | ||||||
| import { InvenTreeTable } from './../InvenTreeTable'; | import { InvenTreeTable } from './../InvenTreeTable'; | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @@ -46,8 +47,109 @@ function stockItemTableColumns(): TableColumn[] { | |||||||
|     { |     { | ||||||
|       accessor: 'quantity', |       accessor: 'quantity', | ||||||
|       sortable: true, |       sortable: true, | ||||||
|       title: t`Stock` |       title: t`Stock`, | ||||||
|       // TODO: Custom renderer for stock quantity |       render: (record) => { | ||||||
|  |         // TODO: Push this out into a custom renderer | ||||||
|  |         let quantity = record?.quantity ?? 0; | ||||||
|  |         let allocated = record?.allocated ?? 0; | ||||||
|  |         let available = quantity - allocated; | ||||||
|  |         let text = quantity; | ||||||
|  |         let part = record?.part_detail ?? {}; | ||||||
|  |         let extra: ReactNode[] = []; | ||||||
|  |         let color = undefined; | ||||||
|  |  | ||||||
|  |         if (record.serial && quantity == 1) { | ||||||
|  |           text = `# ${record.serial}`; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (record.is_building) { | ||||||
|  |           color = 'blue'; | ||||||
|  |           extra.push( | ||||||
|  |             <Text size="sm">{t`This stock item is in production`}</Text> | ||||||
|  |           ); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (record.sales_order) { | ||||||
|  |           extra.push( | ||||||
|  |             <Text size="sm">{t`This stock item has been assigned to a sales order`}</Text> | ||||||
|  |           ); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (record.customer) { | ||||||
|  |           extra.push( | ||||||
|  |             <Text size="sm">{t`This stock item has been assigned to a customer`}</Text> | ||||||
|  |           ); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (record.belongs_to) { | ||||||
|  |           extra.push( | ||||||
|  |             <Text size="sm">{t`This stock item is installed in another stock item`}</Text> | ||||||
|  |           ); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (record.consumed_by) { | ||||||
|  |           extra.push( | ||||||
|  |             <Text size="sm">{t`This stock item has been consumed by a build order`}</Text> | ||||||
|  |           ); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (record.expired) { | ||||||
|  |           extra.push(<Text size="sm">{t`This stock item has expired`}</Text>); | ||||||
|  |         } else if (record.stale) { | ||||||
|  |           extra.push(<Text size="sm">{t`This stock item is stale`}</Text>); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (allocated > 0) { | ||||||
|  |           if (allocated >= quantity) { | ||||||
|  |             color = 'orange'; | ||||||
|  |             extra.push( | ||||||
|  |               <Text size="sm">{t`This stock item is fully allocated`}</Text> | ||||||
|  |             ); | ||||||
|  |           } else { | ||||||
|  |             extra.push( | ||||||
|  |               <Text size="sm">{t`This stock item is partially allocated`}</Text> | ||||||
|  |             ); | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (available != quantity) { | ||||||
|  |           if (available > 0) { | ||||||
|  |             extra.push( | ||||||
|  |               <Text size="sm" color="orange"> | ||||||
|  |                 {t`Available` + `: ${available}`} | ||||||
|  |               </Text> | ||||||
|  |             ); | ||||||
|  |           } else { | ||||||
|  |             extra.push( | ||||||
|  |               <Text size="sm" color="red">{t`No stock available`}</Text> | ||||||
|  |             ); | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (quantity <= 0) { | ||||||
|  |           color = 'red'; | ||||||
|  |           extra.push( | ||||||
|  |             <Text size="sm">{t`This stock item has been depleted`}</Text> | ||||||
|  |           ); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return ( | ||||||
|  |           <TableHoverCard | ||||||
|  |             value={ | ||||||
|  |               <Group spacing="xs" position="left"> | ||||||
|  |                 <Text color={color}>{text}</Text> | ||||||
|  |                 {part.units && ( | ||||||
|  |                   <Text size="xs" color={color}> | ||||||
|  |                     [{part.units}] | ||||||
|  |                   </Text> | ||||||
|  |                 )} | ||||||
|  |               </Group> | ||||||
|  |             } | ||||||
|  |             title={t`Stock Information`} | ||||||
|  |             extra={extra.length > 0 && <Stack spacing="xs">{extra}</Stack>} | ||||||
|  |           /> | ||||||
|  |         ); | ||||||
|  |       } | ||||||
|     }, |     }, | ||||||
|     { |     { | ||||||
|       accessor: 'status', |       accessor: 'status', | ||||||
|   | |||||||
| @@ -94,8 +94,8 @@ export function editPart({ | |||||||
|     title: t`Edit Part`, |     title: t`Edit Part`, | ||||||
|     url: ApiPaths.part_list, |     url: ApiPaths.part_list, | ||||||
|     pk: part_id, |     pk: part_id, | ||||||
|     successMessage: t`Part updated`, |  | ||||||
|     fields: partFields({ editing: true }), |     fields: partFields({ editing: true }), | ||||||
|  |     successMessage: t`Part updated`, | ||||||
|     onFormSuccess: callback |     onFormSuccess: callback | ||||||
|   }); |   }); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -11,12 +11,16 @@ import { openCreateApiForm, openEditApiForm } from '../forms'; | |||||||
| /** | /** | ||||||
|  * Construct a set of fields for creating / editing a StockItem instance |  * Construct a set of fields for creating / editing a StockItem instance | ||||||
|  */ |  */ | ||||||
| export function stockFields({}: {}): ApiFormFieldSet { | export function stockFields({ | ||||||
|  |   create = false | ||||||
|  | }: { | ||||||
|  |   create: boolean; | ||||||
|  | }): ApiFormFieldSet { | ||||||
|   let fields: ApiFormFieldSet = { |   let fields: ApiFormFieldSet = { | ||||||
|     part: { |     part: { | ||||||
|  |       hidden: !create, | ||||||
|       onValueChange: (change: ApiFormChangeCallback) => { |       onValueChange: (change: ApiFormChangeCallback) => { | ||||||
|         // TODO: implement remaining functionality from old stock.py |         // TODO: implement remaining functionality from old stock.py | ||||||
|         console.log('part changed: ', change.value); |  | ||||||
|  |  | ||||||
|         // Clear the 'supplier_part' field if the part is changed |         // Clear the 'supplier_part' field if the part is changed | ||||||
|         change.form.setValues({ |         change.form.setValues({ | ||||||
| @@ -41,15 +45,18 @@ export function stockFields({}: {}): ApiFormFieldSet { | |||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     use_pack_size: { |     use_pack_size: { | ||||||
|  |       hidden: !create, | ||||||
|       description: t`Add given quantity as packs instead of individual items` |       description: t`Add given quantity as packs instead of individual items` | ||||||
|     }, |     }, | ||||||
|     location: { |     location: { | ||||||
|  |       hidden: !create, | ||||||
|       filters: { |       filters: { | ||||||
|         structural: false |         structural: false | ||||||
|       } |       } | ||||||
|       // TODO: icon |       // TODO: icon | ||||||
|     }, |     }, | ||||||
|     quantity: { |     quantity: { | ||||||
|  |       hidden: !create, | ||||||
|       description: t`Enter initial quantity for this stock item` |       description: t`Enter initial quantity for this stock item` | ||||||
|     }, |     }, | ||||||
|     serial_numbers: { |     serial_numbers: { | ||||||
| @@ -57,9 +64,11 @@ export function stockFields({}: {}): ApiFormFieldSet { | |||||||
|       field_type: 'string', |       field_type: 'string', | ||||||
|       label: t`Serial Numbers`, |       label: t`Serial Numbers`, | ||||||
|       description: t`Enter serial numbers for new stock (or leave blank)`, |       description: t`Enter serial numbers for new stock (or leave blank)`, | ||||||
|       required: false |       required: false, | ||||||
|  |       hidden: !create | ||||||
|     }, |     }, | ||||||
|     serial: { |     serial: { | ||||||
|  |       hidden: create | ||||||
|       // TODO: icon |       // TODO: icon | ||||||
|     }, |     }, | ||||||
|     batch: { |     batch: { | ||||||
| @@ -100,7 +109,7 @@ export function createStockItem() { | |||||||
|   openCreateApiForm({ |   openCreateApiForm({ | ||||||
|     name: 'stockitem-create', |     name: 'stockitem-create', | ||||||
|     url: ApiPaths.stock_item_list, |     url: ApiPaths.stock_item_list, | ||||||
|     fields: stockFields({}), |     fields: stockFields({ create: true }), | ||||||
|     title: t`Create Stock Item` |     title: t`Create Stock Item` | ||||||
|   }); |   }); | ||||||
| } | } | ||||||
| @@ -109,12 +118,20 @@ export function createStockItem() { | |||||||
|  * Launch a form to edit an existing StockItem instance |  * Launch a form to edit an existing StockItem instance | ||||||
|  * @param item : primary key of the StockItem to edit |  * @param item : primary key of the StockItem to edit | ||||||
|  */ |  */ | ||||||
| export function editStockItem(item: number) { | export function editStockItem({ | ||||||
|  |   item_id, | ||||||
|  |   callback | ||||||
|  | }: { | ||||||
|  |   item_id: number; | ||||||
|  |   callback?: () => void; | ||||||
|  | }) { | ||||||
|   openEditApiForm({ |   openEditApiForm({ | ||||||
|     name: 'stockitem-edit', |     name: 'stockitem-edit', | ||||||
|     url: ApiPaths.stock_item_list, |     url: ApiPaths.stock_item_list, | ||||||
|     pk: item, |     pk: item_id, | ||||||
|     fields: stockFields({}), |     fields: stockFields({ create: false }), | ||||||
|     title: t`Edit Stock Item` |     title: t`Edit Stock Item`, | ||||||
|  |     successMessage: t`Stock item updated`, | ||||||
|  |     onFormSuccess: callback | ||||||
|   }); |   }); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -30,7 +30,10 @@ import { | |||||||
| import { useMemo, useState } from 'react'; | import { useMemo, useState } from 'react'; | ||||||
| import { useParams } from 'react-router-dom'; | import { useParams } from 'react-router-dom'; | ||||||
|  |  | ||||||
| import { ActionDropdown } from '../../components/items/ActionDropdown'; | import { | ||||||
|  |   ActionDropdown, | ||||||
|  |   BarcodeActionDropdown | ||||||
|  | } from '../../components/items/ActionDropdown'; | ||||||
| import { PageDetail } from '../../components/nav/PageDetail'; | import { PageDetail } from '../../components/nav/PageDetail'; | ||||||
| import { PanelGroup, PanelType } from '../../components/nav/PanelGroup'; | import { PanelGroup, PanelType } from '../../components/nav/PanelGroup'; | ||||||
| import { PartCategoryTree } from '../../components/nav/PartCategoryTree'; | import { PartCategoryTree } from '../../components/nav/PartCategoryTree'; | ||||||
| @@ -236,10 +239,7 @@ export default function PartDetail() { | |||||||
|   const partActions = useMemo(() => { |   const partActions = useMemo(() => { | ||||||
|     // TODO: Disable actions based on user permissions |     // TODO: Disable actions based on user permissions | ||||||
|     return [ |     return [ | ||||||
|       <ActionDropdown |       <BarcodeActionDropdown | ||||||
|         key="barcode" |  | ||||||
|         tooltip={t`Barcode Actions`} |  | ||||||
|         icon={<IconQrcode />} |  | ||||||
|         actions={[ |         actions={[ | ||||||
|           { |           { | ||||||
|             icon: <IconQrcode />, |             icon: <IconQrcode />, | ||||||
|   | |||||||
| @@ -3,27 +3,48 @@ import { Alert, LoadingOverlay, Stack, Text } from '@mantine/core'; | |||||||
| import { | import { | ||||||
|   IconBookmark, |   IconBookmark, | ||||||
|   IconBoxPadding, |   IconBoxPadding, | ||||||
|  |   IconChecklist, | ||||||
|  |   IconCircleCheck, | ||||||
|  |   IconCircleMinus, | ||||||
|  |   IconCirclePlus, | ||||||
|  |   IconCopy, | ||||||
|  |   IconDots, | ||||||
|  |   IconEdit, | ||||||
|   IconHistory, |   IconHistory, | ||||||
|   IconInfoCircle, |   IconInfoCircle, | ||||||
|  |   IconLink, | ||||||
|   IconNotes, |   IconNotes, | ||||||
|  |   IconPackages, | ||||||
|   IconPaperclip, |   IconPaperclip, | ||||||
|   IconSitemap |   IconQrcode, | ||||||
|  |   IconSitemap, | ||||||
|  |   IconTransfer, | ||||||
|  |   IconTrash, | ||||||
|  |   IconUnlink | ||||||
| } from '@tabler/icons-react'; | } from '@tabler/icons-react'; | ||||||
| import { useMemo, useState } from 'react'; | import { useMemo, useState } from 'react'; | ||||||
| import { useParams } from 'react-router-dom'; | import { useParams } from 'react-router-dom'; | ||||||
|  |  | ||||||
|  | import { | ||||||
|  |   ActionDropdown, | ||||||
|  |   BarcodeActionDropdown | ||||||
|  | } from '../../components/items/ActionDropdown'; | ||||||
| import { PlaceholderPanel } from '../../components/items/Placeholder'; | import { PlaceholderPanel } from '../../components/items/Placeholder'; | ||||||
| import { PageDetail } from '../../components/nav/PageDetail'; | import { PageDetail } from '../../components/nav/PageDetail'; | ||||||
| import { PanelGroup, PanelType } from '../../components/nav/PanelGroup'; | import { PanelGroup, PanelType } from '../../components/nav/PanelGroup'; | ||||||
| import { StockLocationTree } from '../../components/nav/StockLocationTree'; | import { StockLocationTree } from '../../components/nav/StockLocationTree'; | ||||||
| import { AttachmentTable } from '../../components/tables/general/AttachmentTable'; | import { AttachmentTable } from '../../components/tables/general/AttachmentTable'; | ||||||
| import { NotesEditor } from '../../components/widgets/MarkdownEditor'; | import { NotesEditor } from '../../components/widgets/MarkdownEditor'; | ||||||
|  | import { editStockItem } from '../../functions/forms/StockForms'; | ||||||
| import { useInstance } from '../../hooks/UseInstance'; | import { useInstance } from '../../hooks/UseInstance'; | ||||||
| import { ApiPaths, apiUrl } from '../../states/ApiState'; | import { ApiPaths, apiUrl } from '../../states/ApiState'; | ||||||
|  | import { useUserState } from '../../states/UserState'; | ||||||
|  |  | ||||||
| export default function StockDetail() { | export default function StockDetail() { | ||||||
|   const { id } = useParams(); |   const { id } = useParams(); | ||||||
|  |  | ||||||
|  |   const user = useUserState(); | ||||||
|  |  | ||||||
|   const [treeOpen, setTreeOpen] = useState(false); |   const [treeOpen, setTreeOpen] = useState(false); | ||||||
|  |  | ||||||
|   const { |   const { | ||||||
| @@ -58,13 +79,22 @@ export default function StockDetail() { | |||||||
|         name: 'allocations', |         name: 'allocations', | ||||||
|         label: t`Allocations`, |         label: t`Allocations`, | ||||||
|         icon: <IconBookmark />, |         icon: <IconBookmark />, | ||||||
|         content: <PlaceholderPanel /> |         content: <PlaceholderPanel />, | ||||||
|  |         hidden: | ||||||
|  |           !stockitem?.part_detail?.salable && !stockitem?.part_detail?.component | ||||||
|  |       }, | ||||||
|  |       { | ||||||
|  |         name: 'testdata', | ||||||
|  |         label: t`Test Data`, | ||||||
|  |         icon: <IconChecklist />, | ||||||
|  |         hidden: !stockitem?.part_detail?.trackable | ||||||
|       }, |       }, | ||||||
|       { |       { | ||||||
|         name: 'installed_items', |         name: 'installed_items', | ||||||
|         label: t`Installed Items`, |         label: t`Installed Items`, | ||||||
|         icon: <IconBoxPadding />, |         icon: <IconBoxPadding />, | ||||||
|         content: <PlaceholderPanel /> |         content: <PlaceholderPanel />, | ||||||
|  |         hidden: !stockitem?.part_detail?.assembly | ||||||
|       }, |       }, | ||||||
|       { |       { | ||||||
|         name: 'child_items', |         name: 'child_items', | ||||||
| @@ -110,6 +140,89 @@ export default function StockDetail() { | |||||||
|     [stockitem] |     [stockitem] | ||||||
|   ); |   ); | ||||||
|  |  | ||||||
|  |   const stockActions = useMemo( | ||||||
|  |     () => /* TODO: Disable actions based on user permissions*/ [ | ||||||
|  |       <BarcodeActionDropdown | ||||||
|  |         actions={[ | ||||||
|  |           { | ||||||
|  |             icon: <IconQrcode />, | ||||||
|  |             name: t`View`, | ||||||
|  |             tooltip: t`View part barcode` | ||||||
|  |           }, | ||||||
|  |           { | ||||||
|  |             icon: <IconLink />, | ||||||
|  |             name: t`Link Barcode`, | ||||||
|  |             tooltip: t`Link custom barcode to stock item`, | ||||||
|  |             disabled: stockitem?.barcode_hash | ||||||
|  |           }, | ||||||
|  |           { | ||||||
|  |             icon: <IconUnlink />, | ||||||
|  |             name: t`Unlink Barcode`, | ||||||
|  |             tooltip: t`Unlink custom barcode from stock item`, | ||||||
|  |             disabled: !stockitem?.barcode_hash | ||||||
|  |           } | ||||||
|  |         ]} | ||||||
|  |       />, | ||||||
|  |       <ActionDropdown | ||||||
|  |         key="operations" | ||||||
|  |         tooltip={t`Stock Operations`} | ||||||
|  |         icon={<IconPackages />} | ||||||
|  |         actions={[ | ||||||
|  |           { | ||||||
|  |             name: t`Count`, | ||||||
|  |             tooltip: t`Count stock`, | ||||||
|  |             icon: <IconCircleCheck color="green" /> | ||||||
|  |           }, | ||||||
|  |           { | ||||||
|  |             name: t`Add`, | ||||||
|  |             tooltip: t`Add stock`, | ||||||
|  |             icon: <IconCirclePlus color="green" /> | ||||||
|  |           }, | ||||||
|  |           { | ||||||
|  |             name: t`Remove`, | ||||||
|  |             tooltip: t`Remove stock`, | ||||||
|  |             icon: <IconCircleMinus color="red" /> | ||||||
|  |           }, | ||||||
|  |           { | ||||||
|  |             name: t`Transfer`, | ||||||
|  |             tooltip: t`Transfer stock`, | ||||||
|  |             icon: <IconTransfer color="blue" /> | ||||||
|  |           } | ||||||
|  |         ]} | ||||||
|  |       />, | ||||||
|  |       <ActionDropdown | ||||||
|  |         key="stock" | ||||||
|  |         // tooltip={t`Stock Actions`} | ||||||
|  |         icon={<IconDots />} | ||||||
|  |         actions={[ | ||||||
|  |           { | ||||||
|  |             name: t`Duplicate`, | ||||||
|  |             tooltip: t`Duplicate stock item`, | ||||||
|  |             icon: <IconCopy /> | ||||||
|  |           }, | ||||||
|  |           { | ||||||
|  |             name: t`Edit`, | ||||||
|  |             tooltip: t`Edit stock item`, | ||||||
|  |             icon: <IconEdit color="blue" />, | ||||||
|  |             onClick: () => { | ||||||
|  |               stockitem.pk && | ||||||
|  |                 editStockItem({ | ||||||
|  |                   item_id: stockitem.pk, | ||||||
|  |                   callback: () => refreshInstance | ||||||
|  |                 }); | ||||||
|  |             } | ||||||
|  |           }, | ||||||
|  |           { | ||||||
|  |             name: t`Delete`, | ||||||
|  |             tooltip: t`Delete stock item`, | ||||||
|  |             icon: <IconTrash color="red" /> | ||||||
|  |           } | ||||||
|  |         ]} | ||||||
|  |       /> | ||||||
|  |     ], | ||||||
|  |     [id, stockitem, user] | ||||||
|  |   ); | ||||||
|  |  | ||||||
|   return ( |   return ( | ||||||
|     <Stack> |     <Stack> | ||||||
|       <LoadingOverlay visible={instanceQuery.isFetching} /> |       <LoadingOverlay visible={instanceQuery.isFetching} /> | ||||||
| @@ -119,8 +232,9 @@ export default function StockDetail() { | |||||||
|         selectedLocation={stockitem?.location} |         selectedLocation={stockitem?.location} | ||||||
|       /> |       /> | ||||||
|       <PageDetail |       <PageDetail | ||||||
|         title={t`Stock Items`} |         title={t`Stock Item`} | ||||||
|         subtitle={stockitem.part_detail?.full_name ?? 'name goes here'} |         subtitle={stockitem.part_detail?.full_name} | ||||||
|  |         imageUrl={stockitem.part_detail?.thumbnail} | ||||||
|         detail={ |         detail={ | ||||||
|           <Alert color="teal" title="Stock Item"> |           <Alert color="teal" title="Stock Item"> | ||||||
|             <Text>Quantity: {stockitem.quantity ?? 'idk'}</Text> |             <Text>Quantity: {stockitem.quantity ?? 'idk'}</Text> | ||||||
| @@ -130,6 +244,7 @@ export default function StockDetail() { | |||||||
|         breadcrumbAction={() => { |         breadcrumbAction={() => { | ||||||
|           setTreeOpen(true); |           setTreeOpen(true); | ||||||
|         }} |         }} | ||||||
|  |         actions={stockActions} | ||||||
|       /> |       /> | ||||||
|       <PanelGroup pageKey="stockitem" panels={stockPanels} /> |       <PanelGroup pageKey="stockitem" panels={stockPanels} /> | ||||||
|     </Stack> |     </Stack> | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user