[PUI] Tweaks (#7843)

* Part test template table updates

* Allow export from part test template table

* Allow actions for build lines

* Updates to BuildLine table
This commit is contained in:
Oliver 2024-08-09 18:24:19 +10:00 committed by GitHub
parent 21f623eea8
commit d68d52ba88
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 97 additions and 45 deletions

View File

@ -675,7 +675,7 @@ export default function PartDetail() {
icon: <IconTestPipe />, icon: <IconTestPipe />,
hidden: !part.trackable, hidden: !part.trackable,
content: part?.pk ? ( content: part?.pk ? (
<PartTestTemplateTable partId={part?.pk} /> <PartTestTemplateTable partId={part?.pk} partLocked={part.locked} />
) : ( ) : (
<Skeleton /> <Skeleton />
) )

View File

@ -24,9 +24,11 @@ import { TableHoverCard } from '../TableHoverCard';
export default function BuildLineTable({ export default function BuildLineTable({
buildId, buildId,
outputId,
params = {} params = {}
}: { }: {
buildId: number; buildId: number;
outputId?: number;
params?: any; params?: any;
}) { }) {
const table = useTable('buildline'); const table = useTable('buildline');
@ -237,35 +239,42 @@ export default function BuildLineTable({
const rowActions = useCallback( const rowActions = useCallback(
(record: any) => { (record: any) => {
let part = record.part_detail; let part = record.part_detail ?? {};
// Consumable items have no appropriate actions // Consumable items have no appropriate actions
if (record?.bom_item_detail?.consumable) { if (record?.bom_item_detail?.consumable) {
return []; return [];
} }
// Tracked items must be allocated to a particular output const hasOutput = !!outputId;
if (record?.part_detail?.trackable) {
return []; // 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 [ return [
{ {
icon: <IconArrowRight />, icon: <IconArrowRight />,
title: t`Allocate Stock`, title: t`Allocate Stock`,
hidden: record.allocated >= record.quantity, hidden: !canAllocate,
color: 'green' color: 'green'
}, },
{ {
icon: <IconShoppingCart />, icon: <IconShoppingCart />,
title: t`Order Stock`, title: t`Order Stock`,
hidden: !part?.purchaseable, hidden: !canOrder,
color: 'blue' color: 'blue'
}, },
{ {
icon: <IconTool />, icon: <IconTool />,
title: t`Build Stock`, title: t`Build Stock`,
hidden: !part?.assembly || !user.hasAddRole(UserRoles.build), hidden: !canBuild,
color: 'blue', color: 'blue',
onClick: () => { onClick: () => {
setInitialData({ setInitialData({
@ -278,7 +287,7 @@ export default function BuildLineTable({
} }
]; ];
}, },
[user] [user, outputId]
); );
return ( return (

View File

@ -1,6 +1,7 @@
import { Trans, t } from '@lingui/macro'; import { Trans, t } from '@lingui/macro';
import { Alert, Badge, Text } from '@mantine/core'; import { Alert, Badge, Stack, Text } from '@mantine/core';
import { useCallback, useMemo, useState } from 'react'; import { IconArrowRight, IconLock } from '@tabler/icons-react';
import { ReactNode, useCallback, useMemo, useState } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { AddItemButton } from '../../components/buttons/AddItemButton'; import { AddItemButton } from '../../components/buttons/AddItemButton';
@ -22,8 +23,15 @@ import { BooleanColumn, DescriptionColumn } from '../ColumnRenderers';
import { TableFilter } from '../Filter'; import { TableFilter } from '../Filter';
import { InvenTreeTable } from '../InvenTreeTable'; import { InvenTreeTable } from '../InvenTreeTable';
import { RowDeleteAction, RowEditAction } from '../RowActions'; 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 table = useTable('part-test-template');
const user = useUserState(); const user = useUserState();
const navigate = useNavigate(); const navigate = useNavigate();
@ -35,13 +43,27 @@ export default function PartTestTemplateTable({ partId }: { partId: number }) {
switchable: false, switchable: false,
sortable: true, sortable: true,
render: (record: any) => { render: (record: any) => {
let extra: ReactNode[] = [];
if (record.part != partId) {
extra.push(
<Text size="sm">{t`Test is defined for a parent template part`}</Text>
);
}
return ( return (
<Text <TableHoverCard
fw={record.required && 700} value={
c={record.enabled ? undefined : 'red'} <Text
> fw={record.required && 700}
{record.test_name} c={record.enabled ? undefined : 'red'}
</Text> >
{record.test_name}
</Text>
}
title={t`Template Details`}
extra={extra}
/>
); );
} }
}, },
@ -175,20 +197,28 @@ export default function PartTestTemplateTable({ partId }: { partId: number }) {
const can_delete = user.hasDeleteRole(UserRoles.part); const can_delete = user.hasDeleteRole(UserRoles.part);
if (record.part != partId) { if (record.part != partId) {
// No actions, as this test is defined for a parent part // This test is defined for a parent part
return []; return [
{
icon: <IconArrowRight />,
title: t`View Parent Part`,
onClick: () => {
navigate(getDetailUrl(ModelType.part, record.part));
}
}
];
} }
return [ return [
RowEditAction({ RowEditAction({
hidden: !can_edit, hidden: partLocked || !can_edit,
onClick: () => { onClick: () => {
setSelectedTest(record.pk); setSelectedTest(record.pk);
editTestTemplate.open(); editTestTemplate.open();
} }
}), }),
RowDeleteAction({ RowDeleteAction({
hidden: !can_delete, hidden: partLocked || !can_delete,
onClick: () => { onClick: () => {
setSelectedTest(record.pk); setSelectedTest(record.pk);
deleteTestTemplate.open(); deleteTestTemplate.open();
@ -196,7 +226,7 @@ export default function PartTestTemplateTable({ partId }: { partId: number }) {
}) })
]; ];
}, },
[user, partId] [user, partId, partLocked]
); );
const tableActions = useMemo(() => { const tableActions = useMemo(() => {
@ -206,36 +236,49 @@ export default function PartTestTemplateTable({ partId }: { partId: number }) {
<AddItemButton <AddItemButton
tooltip={t`Add Test Template`} tooltip={t`Add Test Template`}
onClick={() => newTestTemplate.open()} onClick={() => newTestTemplate.open()}
hidden={!can_add} hidden={partLocked || !can_add}
/> />
]; ];
}, [user]); }, [user, partLocked]);
return ( return (
<> <>
{newTestTemplate.modal} {newTestTemplate.modal}
{editTestTemplate.modal} {editTestTemplate.modal}
{deleteTestTemplate.modal} {deleteTestTemplate.modal}
<InvenTreeTable <Stack gap="xs">
url={apiUrl(ApiEndpoints.part_test_template_list)} {partLocked && (
tableState={table} <Alert
columns={tableColumns} title={t`Part is Locked`}
props={{ color="orange"
params: { icon={<IconLock />}
part: partId, p="xs"
part_detail: true >
}, <Text>{t`Part templates cannot be edited, as the part is locked`}</Text>
tableFilters: tableFilters, </Alert>
tableActions: tableActions, )}
rowActions: rowActions, <InvenTreeTable
onRowClick: (row) => { url={apiUrl(ApiEndpoints.part_test_template_list)}
if (row.part && row.part != partId) { tableState={table}
// This test is defined for a different part columns={tableColumns}
navigate(getDetailUrl(ModelType.part, row.part)); props={{
params: {
part: partId,
part_detail: true
},
tableFilters: tableFilters,
tableActions: tableActions,
enableDownload: true,
rowActions: rowActions,
onRowClick: (row) => {
if (row.part && row.part != partId) {
// This test is defined for a different part
navigate(getDetailUrl(ModelType.part, row.part));
}
} }
} }}
}} />
/> </Stack>
</> </>
); );
} }