diff --git a/src/frontend/src/pages/part/PartDetail.tsx b/src/frontend/src/pages/part/PartDetail.tsx index 64cb40df98..821766af5f 100644 --- a/src/frontend/src/pages/part/PartDetail.tsx +++ b/src/frontend/src/pages/part/PartDetail.tsx @@ -675,7 +675,7 @@ export default function PartDetail() { icon: , hidden: !part.trackable, content: part?.pk ? ( - + ) : ( ) diff --git a/src/frontend/src/tables/build/BuildLineTable.tsx b/src/frontend/src/tables/build/BuildLineTable.tsx index 1940a95de2..f51e5d711a 100644 --- a/src/frontend/src/tables/build/BuildLineTable.tsx +++ b/src/frontend/src/tables/build/BuildLineTable.tsx @@ -24,9 +24,11 @@ import { TableHoverCard } from '../TableHoverCard'; export default function BuildLineTable({ buildId, + outputId, params = {} }: { buildId: number; + outputId?: number; params?: any; }) { const table = useTable('buildline'); @@ -237,35 +239,42 @@ export default function BuildLineTable({ const rowActions = useCallback( (record: any) => { - let part = record.part_detail; + let part = record.part_detail ?? {}; // Consumable items have no appropriate actions if (record?.bom_item_detail?.consumable) { return []; } - // Tracked items must be allocated to a particular output - if (record?.part_detail?.trackable) { - return []; - } + const hasOutput = !!outputId; + + // Can allocate + let canAllocate = + user.hasChangeRole(UserRoles.build) && + record.allocated < record.quantity && + record.trackable == hasOutput; + + let canOrder = + user.hasAddRole(UserRoles.purchase_order) && part.purchaseable; + let canBuild = user.hasAddRole(UserRoles.build) && part.assembly; return [ { icon: , title: t`Allocate Stock`, - hidden: record.allocated >= record.quantity, + hidden: !canAllocate, color: 'green' }, { icon: , title: t`Order Stock`, - hidden: !part?.purchaseable, + hidden: !canOrder, color: 'blue' }, { icon: , title: t`Build Stock`, - hidden: !part?.assembly || !user.hasAddRole(UserRoles.build), + hidden: !canBuild, color: 'blue', onClick: () => { setInitialData({ @@ -278,7 +287,7 @@ export default function BuildLineTable({ } ]; }, - [user] + [user, outputId] ); return ( diff --git a/src/frontend/src/tables/part/PartTestTemplateTable.tsx b/src/frontend/src/tables/part/PartTestTemplateTable.tsx index 6dd0af662d..1545663a2e 100644 --- a/src/frontend/src/tables/part/PartTestTemplateTable.tsx +++ b/src/frontend/src/tables/part/PartTestTemplateTable.tsx @@ -1,6 +1,7 @@ import { Trans, t } from '@lingui/macro'; -import { Alert, Badge, Text } from '@mantine/core'; -import { useCallback, useMemo, useState } from 'react'; +import { Alert, Badge, Stack, Text } from '@mantine/core'; +import { IconArrowRight, IconLock } from '@tabler/icons-react'; +import { ReactNode, useCallback, useMemo, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { AddItemButton } from '../../components/buttons/AddItemButton'; @@ -22,8 +23,15 @@ import { BooleanColumn, DescriptionColumn } from '../ColumnRenderers'; import { TableFilter } from '../Filter'; import { InvenTreeTable } from '../InvenTreeTable'; import { RowDeleteAction, RowEditAction } from '../RowActions'; +import { TableHoverCard } from '../TableHoverCard'; -export default function PartTestTemplateTable({ partId }: { partId: number }) { +export default function PartTestTemplateTable({ + partId, + partLocked +}: { + partId: number; + partLocked?: boolean; +}) { const table = useTable('part-test-template'); const user = useUserState(); const navigate = useNavigate(); @@ -35,13 +43,27 @@ export default function PartTestTemplateTable({ partId }: { partId: number }) { switchable: false, sortable: true, render: (record: any) => { + let extra: ReactNode[] = []; + + if (record.part != partId) { + extra.push( + {t`Test is defined for a parent template part`} + ); + } + return ( - - {record.test_name} - + + {record.test_name} + + } + title={t`Template Details`} + extra={extra} + /> ); } }, @@ -175,20 +197,28 @@ export default function PartTestTemplateTable({ partId }: { partId: number }) { const can_delete = user.hasDeleteRole(UserRoles.part); if (record.part != partId) { - // No actions, as this test is defined for a parent part - return []; + // This test is defined for a parent part + return [ + { + icon: , + title: t`View Parent Part`, + onClick: () => { + navigate(getDetailUrl(ModelType.part, record.part)); + } + } + ]; } return [ RowEditAction({ - hidden: !can_edit, + hidden: partLocked || !can_edit, onClick: () => { setSelectedTest(record.pk); editTestTemplate.open(); } }), RowDeleteAction({ - hidden: !can_delete, + hidden: partLocked || !can_delete, onClick: () => { setSelectedTest(record.pk); deleteTestTemplate.open(); @@ -196,7 +226,7 @@ export default function PartTestTemplateTable({ partId }: { partId: number }) { }) ]; }, - [user, partId] + [user, partId, partLocked] ); const tableActions = useMemo(() => { @@ -206,36 +236,49 @@ export default function PartTestTemplateTable({ partId }: { partId: number }) { newTestTemplate.open()} - hidden={!can_add} + hidden={partLocked || !can_add} /> ]; - }, [user]); + }, [user, partLocked]); return ( <> {newTestTemplate.modal} {editTestTemplate.modal} {deleteTestTemplate.modal} - { - if (row.part && row.part != partId) { - // This test is defined for a different part - navigate(getDetailUrl(ModelType.part, row.part)); + + {partLocked && ( + } + p="xs" + > + {t`Part templates cannot be edited, as the part is locked`} + + )} + { + if (row.part && row.part != partId) { + // This test is defined for a different part + navigate(getDetailUrl(ModelType.part, row.part)); + } } - } - }} - /> + }} + /> + ); }