mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
[PUI] Add simple "related parts" table (#5530)
* Add simple "related parts" table - Still needs method to create a new related part - Should allow user to click through to related parts - Need to implement the "delete related part" functionality * Fix image preview * Add action to delete part relationship * Implement function to add new related part * Implement simple "click through" for the related parts table - Will need to be improved later on * fix
This commit is contained in:
parent
816b60850d
commit
666fe90c5c
@ -48,7 +48,7 @@ export interface ApiFormProps {
|
||||
url: string;
|
||||
pk?: number;
|
||||
title: string;
|
||||
fields: ApiFormFieldSet;
|
||||
fields?: ApiFormFieldSet;
|
||||
cancelText?: string;
|
||||
submitText?: string;
|
||||
submitColor?: string;
|
||||
@ -118,7 +118,7 @@ export function ApiForm({
|
||||
.get(url)
|
||||
.then((response) => {
|
||||
// Update form values, but only for the fields specified for the form
|
||||
Object.keys(props.fields).forEach((fieldName) => {
|
||||
Object.keys(props.fields ?? {}).forEach((fieldName) => {
|
||||
if (fieldName in response.data) {
|
||||
form.setValues({
|
||||
[fieldName]: response.data[fieldName]
|
||||
@ -137,7 +137,7 @@ export function ApiForm({
|
||||
// Fetch initial data on form load
|
||||
useEffect(() => {
|
||||
// Provide initial form data
|
||||
Object.entries(props.fields).forEach(([fieldName, field]) => {
|
||||
Object.entries(props.fields ?? {}).forEach(([fieldName, field]) => {
|
||||
if (field.value !== undefined) {
|
||||
form.setValues({
|
||||
[fieldName]: field.value
|
||||
@ -272,7 +272,7 @@ export function ApiForm({
|
||||
{preFormElement}
|
||||
<ScrollArea>
|
||||
<Stack spacing="xs">
|
||||
{Object.entries(props.fields).map(
|
||||
{Object.entries(props.fields ?? {}).map(
|
||||
([fieldName, field]) =>
|
||||
!field.hidden && (
|
||||
<ApiFormField
|
||||
|
@ -8,7 +8,7 @@ import { api } from '../../App';
|
||||
export function Thumbnail({
|
||||
src,
|
||||
alt = t`Thumbnail`,
|
||||
size = 24
|
||||
size = 20
|
||||
}: {
|
||||
src: string;
|
||||
alt?: string;
|
||||
|
@ -174,7 +174,7 @@ export function AttachmentTable({
|
||||
});
|
||||
}
|
||||
|
||||
function customActionGroups(): ReactNode[] {
|
||||
const customActionGroups: ReactNode[] = useMemo(() => {
|
||||
let actions = [];
|
||||
|
||||
if (allowEdit) {
|
||||
@ -218,7 +218,7 @@ export function AttachmentTable({
|
||||
}
|
||||
|
||||
return actions;
|
||||
}
|
||||
}, [allowEdit]);
|
||||
|
||||
return (
|
||||
<Stack spacing="xs">
|
||||
@ -229,7 +229,7 @@ export function AttachmentTable({
|
||||
params={{
|
||||
[model]: pk
|
||||
}}
|
||||
customActionGroups={customActionGroups()}
|
||||
customActionGroups={customActionGroups}
|
||||
columns={tableColumns}
|
||||
rowActions={allowEdit && allowDelete ? rowActions : undefined}
|
||||
/>
|
||||
|
@ -45,7 +45,7 @@ export function RowActions({
|
||||
icon={action.icon}
|
||||
title={action.tooltip || action.title}
|
||||
>
|
||||
<Text size="sm" color={action.color}>
|
||||
<Text size="xs" color={action.color}>
|
||||
{action.title}
|
||||
</Text>
|
||||
</Menu.Item>
|
||||
|
129
src/frontend/src/components/tables/part/RelatedPartTable.tsx
Normal file
129
src/frontend/src/components/tables/part/RelatedPartTable.tsx
Normal file
@ -0,0 +1,129 @@
|
||||
import { t } from '@lingui/macro';
|
||||
import { ActionIcon, Group, Text, Tooltip } from '@mantine/core';
|
||||
import { IconLayersLinked } from '@tabler/icons-react';
|
||||
import { ReactNode, useCallback, useMemo } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { openCreateApiForm, openDeleteApiForm } from '../../../functions/forms';
|
||||
import { useTableRefresh } from '../../../hooks/TableRefresh';
|
||||
import { Thumbnail } from '../../items/Thumbnail';
|
||||
import { TableColumn } from '../Column';
|
||||
import { InvenTreeTable } from '../InvenTreeTable';
|
||||
|
||||
export function RelatedPartTable({ partId }: { partId: number }): ReactNode {
|
||||
const { refreshId, refreshTable } = useTableRefresh();
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
// Construct table columns for this table
|
||||
const tableColumns: TableColumn[] = useMemo(() => {
|
||||
function getPart(record: any) {
|
||||
if (record.part_1 == partId) {
|
||||
return record.part_2_detail;
|
||||
} else {
|
||||
return record.part_1_detail;
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
accessor: 'part',
|
||||
title: t`Part`,
|
||||
noWrap: true,
|
||||
render: (record: any) => {
|
||||
let part = getPart(record);
|
||||
return (
|
||||
<Group
|
||||
onClick={() => {
|
||||
navigate(`/part/${part.pk}/`);
|
||||
}}
|
||||
>
|
||||
<Thumbnail src={part.thumbnail || part.image} />
|
||||
<Text>{part.name}</Text>
|
||||
</Group>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
accessor: 'description',
|
||||
title: t`Description`,
|
||||
ellipsis: true,
|
||||
render: (record: any) => {
|
||||
return getPart(record).description;
|
||||
}
|
||||
}
|
||||
];
|
||||
}, []);
|
||||
|
||||
const addRelatedPart = useCallback(() => {
|
||||
openCreateApiForm({
|
||||
name: 'add-related-part',
|
||||
title: t`Add Related Part`,
|
||||
url: '/part/related/',
|
||||
fields: {
|
||||
part_1: {
|
||||
hidden: true,
|
||||
value: partId
|
||||
},
|
||||
part_2: {
|
||||
label: t`Related Part`
|
||||
}
|
||||
},
|
||||
successMessage: t`Related part added`,
|
||||
onFormSuccess: refreshTable
|
||||
});
|
||||
}, []);
|
||||
|
||||
const customActions: ReactNode[] = useMemo(() => {
|
||||
// TODO: Hide if user does not have permission to edit parts
|
||||
let actions = [];
|
||||
|
||||
actions.push(
|
||||
<Tooltip label={t`Add related part`}>
|
||||
<ActionIcon radius="sm" onClick={addRelatedPart}>
|
||||
<IconLayersLinked />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
return actions;
|
||||
}, []);
|
||||
|
||||
// Generate row actions
|
||||
// TODO: Hide if user does not have permission to edit parts
|
||||
const rowActions = useCallback((record: any) => {
|
||||
return [
|
||||
{
|
||||
title: t`Delete`,
|
||||
color: 'red',
|
||||
onClick: () => {
|
||||
openDeleteApiForm({
|
||||
name: 'delete-related-part',
|
||||
url: '/part/related/',
|
||||
pk: record.pk,
|
||||
title: t`Delete Related Part`,
|
||||
successMessage: t`Related part deleted`,
|
||||
preFormContent: (
|
||||
<Text>{t`Are you sure you want to remove this relationship?`}</Text>
|
||||
),
|
||||
onFormSuccess: refreshTable
|
||||
});
|
||||
}
|
||||
}
|
||||
];
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<InvenTreeTable
|
||||
url="/part/related/"
|
||||
tableKey="related-part-table"
|
||||
refreshId={refreshId}
|
||||
params={{
|
||||
part: partId
|
||||
}}
|
||||
rowActions={rowActions}
|
||||
columns={tableColumns}
|
||||
customActionGroups={customActions}
|
||||
/>
|
||||
);
|
||||
}
|
@ -175,7 +175,8 @@ export function openDeleteApiForm(props: ApiFormProps) {
|
||||
...props,
|
||||
method: 'DELETE',
|
||||
submitText: t`Delete`,
|
||||
submitColor: 'red'
|
||||
submitColor: 'red',
|
||||
fields: {}
|
||||
};
|
||||
|
||||
openModalApiForm(deleteProps);
|
||||
|
@ -3,14 +3,11 @@ import {
|
||||
Button,
|
||||
Group,
|
||||
LoadingOverlay,
|
||||
Skeleton,
|
||||
Space,
|
||||
Stack,
|
||||
Tabs,
|
||||
Text
|
||||
} from '@mantine/core';
|
||||
import {
|
||||
IconBox,
|
||||
IconBuilding,
|
||||
IconCurrencyDollar,
|
||||
IconInfoCircle,
|
||||
@ -34,6 +31,7 @@ import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { api } from '../../App';
|
||||
import { PanelGroup, PanelType } from '../../components/nav/PanelGroup';
|
||||
import { AttachmentTable } from '../../components/tables/AttachmentTable';
|
||||
import { RelatedPartTable } from '../../components/tables/part/RelatedPartTable';
|
||||
import { StockItemTable } from '../../components/tables/stock/StockItemTable';
|
||||
import {
|
||||
MarkdownEditor,
|
||||
@ -128,7 +126,7 @@ export default function PartDetail() {
|
||||
name: 'related_parts',
|
||||
label: t`Related Parts`,
|
||||
icon: <IconLayersLinked size="18" />,
|
||||
content: <Text>part related parts go here</Text>
|
||||
content: partRelatedTab()
|
||||
},
|
||||
{
|
||||
name: 'attachments',
|
||||
@ -171,6 +169,9 @@ export default function PartDetail() {
|
||||
);
|
||||
}
|
||||
|
||||
function partRelatedTab(): React.ReactNode {
|
||||
return <RelatedPartTable partId={part.pk ?? -1} />;
|
||||
}
|
||||
function partNotesTab(): React.ReactNode {
|
||||
// TODO: Set edit permission based on user permissions
|
||||
return (
|
||||
|
Loading…
Reference in New Issue
Block a user