mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
[React] SupplierPart table (#5833)
* Fix TableHoverCard component * Improving handling of very wide table cells * Update panels for PartDetail * Refactor <Thumbnail> component * Add SupplierPart table * Refactor forms - Do not need to specify custom form name any more * More fixes for modal forms * Refactor forms field code * Add generic row action components for edit and delete * Add placeholder comments * Add ability to edit supplier part from table * Create supplier part * Add missing import * Revert scroll behaviour for wide cells - Does not play nice on chrome * Add placeholder panel for part manufacturers * Fix inline renderer for manufacturerpart * Cleanup unused imports * Add icons to supplier part fields * Increase size of form titles * Another fix
This commit is contained in:
parent
a11418398f
commit
f71322ecd3
@ -372,6 +372,7 @@ class SupplierPartSerializer(InvenTreeTagModelSerializer):
|
||||
|
||||
# Annotated field showing total in-stock quantity
|
||||
in_stock = serializers.FloatField(read_only=True)
|
||||
available = serializers.FloatField(required=False)
|
||||
|
||||
part_detail = PartBriefSerializer(source='part', many=False, read_only=True)
|
||||
|
||||
|
@ -16,7 +16,6 @@ import { ApiFormField, ApiFormFieldSet } from './fields/ApiFormField';
|
||||
|
||||
/**
|
||||
* Properties for the ApiForm component
|
||||
* @param name : The name (identifier) for this form
|
||||
* @param url : The API endpoint to fetch the form data from
|
||||
* @param pk : Optional primary-key value when editing an existing object
|
||||
* @param title : The title to display in the form header
|
||||
@ -35,7 +34,6 @@ import { ApiFormField, ApiFormFieldSet } from './fields/ApiFormField';
|
||||
* @param onFormError : A callback function to call when the form is submitted with errors.
|
||||
*/
|
||||
export interface ApiFormProps {
|
||||
name: string;
|
||||
url: ApiPaths;
|
||||
pk?: number | string | undefined;
|
||||
title: string;
|
||||
@ -104,7 +102,7 @@ export function ApiForm({
|
||||
// Query manager for retrieiving initial data from the server
|
||||
const initialDataQuery = useQuery({
|
||||
enabled: false,
|
||||
queryKey: ['form-initial-data', props.name, props.url, props.pk],
|
||||
queryKey: ['form-initial-data', modalId, props.method, props.url, props.pk],
|
||||
queryFn: async () => {
|
||||
return api
|
||||
.get(url)
|
||||
@ -150,7 +148,13 @@ export function ApiForm({
|
||||
// Fetch initial data if the fetchInitialData property is set
|
||||
if (props.fetchInitialData) {
|
||||
queryClient.removeQueries({
|
||||
queryKey: ['form-initial-data', props.name, props.url, props.pk]
|
||||
queryKey: [
|
||||
'form-initial-data',
|
||||
modalId,
|
||||
props.method,
|
||||
props.url,
|
||||
props.pk
|
||||
]
|
||||
});
|
||||
initialDataQuery.refetch();
|
||||
}
|
||||
@ -159,7 +163,7 @@ export function ApiForm({
|
||||
// Query manager for submitting data
|
||||
const submitQuery = useQuery({
|
||||
enabled: false,
|
||||
queryKey: ['form-submit', props.name, props.url, props.pk],
|
||||
queryKey: ['form-submit', modalId, props.method, props.url, props.pk],
|
||||
queryFn: async () => {
|
||||
let method = props.method?.toLowerCase() ?? 'get';
|
||||
|
||||
|
@ -2,35 +2,43 @@ import { t } from '@lingui/macro';
|
||||
import { Anchor } from '@mantine/core';
|
||||
import { Group } from '@mantine/core';
|
||||
import { Text } from '@mantine/core';
|
||||
import { useMemo } from 'react';
|
||||
import { ReactNode, useMemo } from 'react';
|
||||
|
||||
import { ApiImage } from './ApiImage';
|
||||
|
||||
/*
|
||||
* Render an image, loaded via the API
|
||||
*/
|
||||
export function Thumbnail({
|
||||
src,
|
||||
alt = t`Thumbnail`,
|
||||
size = 20
|
||||
size = 20,
|
||||
text
|
||||
}: {
|
||||
src?: string | undefined;
|
||||
alt?: string;
|
||||
size?: number;
|
||||
text?: ReactNode;
|
||||
}) {
|
||||
// TODO: Use HoverCard to display a larger version of the image
|
||||
const backup_image = '/static/img/blank_image.png';
|
||||
|
||||
return (
|
||||
<ApiImage
|
||||
src={src || '/static/img/blank_image.png'}
|
||||
alt={alt}
|
||||
width={size}
|
||||
fit="contain"
|
||||
radius="xs"
|
||||
withPlaceholder
|
||||
imageProps={{
|
||||
style: {
|
||||
maxHeight: size
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Group align="left" spacing="xs" noWrap={true}>
|
||||
<ApiImage
|
||||
src={src || backup_image}
|
||||
alt={alt}
|
||||
width={size}
|
||||
fit="contain"
|
||||
radius="xs"
|
||||
withPlaceholder
|
||||
imageProps={{
|
||||
style: {
|
||||
maxHeight: size
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{text}
|
||||
</Group>
|
||||
);
|
||||
}
|
||||
|
||||
@ -39,7 +47,7 @@ export function ThumbnailHoverCard({
|
||||
text,
|
||||
link = '',
|
||||
alt = t`Thumbnail`,
|
||||
size = 24
|
||||
size = 20
|
||||
}: {
|
||||
src: string;
|
||||
text: string;
|
||||
@ -56,12 +64,13 @@ export function ThumbnailHoverCard({
|
||||
);
|
||||
}, [src, text, alt, size]);
|
||||
|
||||
if (link)
|
||||
if (link) {
|
||||
return (
|
||||
<Anchor href={link} style={{ textDecoration: 'none' }}>
|
||||
{card}
|
||||
</Anchor>
|
||||
);
|
||||
}
|
||||
|
||||
return <div>{card}</div>;
|
||||
}
|
||||
|
@ -70,15 +70,20 @@ export function RenderSupplierPart({ instance }: { instance: any }): ReactNode {
|
||||
/**
|
||||
* Inline rendering of a single ManufacturerPart instance
|
||||
*/
|
||||
export function ManufacturerPart({ instance }: { instance: any }): ReactNode {
|
||||
let supplier = instance.supplier_detail ?? {};
|
||||
export function RenderManufacturerPart({
|
||||
instance
|
||||
}: {
|
||||
instance: any;
|
||||
}): ReactNode {
|
||||
let part = instance.part_detail ?? {};
|
||||
let manufacturer = instance.manufacturer_detail ?? {};
|
||||
|
||||
let text = instance.SKU;
|
||||
|
||||
if (supplier.name) {
|
||||
text = `${supplier.name} | ${text}`;
|
||||
}
|
||||
|
||||
return <RenderInlineModel primary={text} secondary={part.full_name} />;
|
||||
return (
|
||||
<RenderInlineModel
|
||||
primary={manufacturer.name}
|
||||
secondary={instance.MPN}
|
||||
suffix={part.full_name}
|
||||
image={manufacturer?.thumnbnail ?? manufacturer.image}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import {
|
||||
RenderAddress,
|
||||
RenderCompany,
|
||||
RenderContact,
|
||||
RenderManufacturerPart,
|
||||
RenderSupplierPart
|
||||
} from './Company';
|
||||
import { ModelType } from './ModelType';
|
||||
@ -41,6 +42,7 @@ const RendererLookup: EnumDictionary<
|
||||
[ModelType.build]: RenderBuildOrder,
|
||||
[ModelType.company]: RenderCompany,
|
||||
[ModelType.contact]: RenderContact,
|
||||
[ModelType.manufacturerpart]: RenderManufacturerPart,
|
||||
[ModelType.owner]: RenderOwner,
|
||||
[ModelType.part]: RenderPart,
|
||||
[ModelType.partcategory]: RenderPartCategory,
|
||||
@ -54,8 +56,7 @@ const RendererLookup: EnumDictionary<
|
||||
[ModelType.stockitem]: RenderStockItem,
|
||||
[ModelType.stockhistory]: RenderStockItem,
|
||||
[ModelType.supplierpart]: RenderSupplierPart,
|
||||
[ModelType.user]: RenderUser,
|
||||
[ModelType.manufacturerpart]: RenderPart
|
||||
[ModelType.user]: RenderUser
|
||||
};
|
||||
|
||||
// import { ApiFormFieldType } from "../forms/fields/ApiFormField";
|
||||
|
@ -51,7 +51,6 @@ function SettingValue({
|
||||
}
|
||||
|
||||
openModalApiForm({
|
||||
name: 'setting-edit',
|
||||
url: settingsState.endpoint,
|
||||
pk: setting.key,
|
||||
method: 'PATCH',
|
||||
|
@ -524,7 +524,11 @@ export function InvenTreeTable({
|
||||
onRowClick={tableProps.onRowClick}
|
||||
defaultColumnProps={{
|
||||
noWrap: true,
|
||||
textAlignment: 'left'
|
||||
textAlignment: 'left',
|
||||
cellsStyle: {
|
||||
// TODO: Need a better way of handling "wide" cells,
|
||||
overflow: 'hidden'
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
|
@ -2,7 +2,7 @@ import { t } from '@lingui/macro';
|
||||
import { ActionIcon, Tooltip } from '@mantine/core';
|
||||
import { Menu, Text } from '@mantine/core';
|
||||
import { IconDots } from '@tabler/icons-react';
|
||||
import { ReactNode, useState } from 'react';
|
||||
import { ReactNode, useMemo, useState } from 'react';
|
||||
|
||||
import { notYetImplemented } from '../../functions/notifications';
|
||||
|
||||
@ -12,9 +12,41 @@ export type RowAction = {
|
||||
color?: string;
|
||||
onClick?: () => void;
|
||||
tooltip?: string;
|
||||
icon?: ReactNode;
|
||||
hidden?: boolean;
|
||||
};
|
||||
|
||||
// Component for ediitng a row in a table
|
||||
export function RowEditAction({
|
||||
onClick,
|
||||
hidden
|
||||
}: {
|
||||
onClick?: () => void;
|
||||
hidden?: boolean;
|
||||
}): RowAction {
|
||||
return {
|
||||
title: t`Edit`,
|
||||
color: 'green',
|
||||
onClick: onClick,
|
||||
hidden: hidden
|
||||
};
|
||||
}
|
||||
|
||||
// Component for deleting a row in a table
|
||||
export function RowDeleteAction({
|
||||
onClick,
|
||||
hidden
|
||||
}: {
|
||||
onClick?: () => void;
|
||||
hidden?: boolean;
|
||||
}): RowAction {
|
||||
return {
|
||||
title: t`Delete`,
|
||||
color: 'red',
|
||||
onClick: onClick,
|
||||
hidden: hidden
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Component for displaying actions for a row in a table.
|
||||
* Displays a simple dropdown menu with a list of actions.
|
||||
@ -39,8 +71,12 @@ export function RowActions({
|
||||
|
||||
const [opened, setOpened] = useState(false);
|
||||
|
||||
const visibleActions = useMemo(() => {
|
||||
return actions.filter((action) => !action.hidden);
|
||||
}, [actions]);
|
||||
|
||||
return (
|
||||
actions.length > 0 && (
|
||||
visibleActions.length > 0 && (
|
||||
<Menu
|
||||
withinPortal={true}
|
||||
disabled={disabled}
|
||||
@ -61,7 +97,7 @@ export function RowActions({
|
||||
</Menu.Target>
|
||||
<Menu.Dropdown>
|
||||
<Menu.Label>{title || t`Actions`}</Menu.Label>
|
||||
{actions.map((action, idx) => (
|
||||
{visibleActions.map((action, idx) => (
|
||||
<Menu.Item
|
||||
key={idx}
|
||||
onClick={(event) => {
|
||||
@ -75,7 +111,6 @@ export function RowActions({
|
||||
notYetImplemented();
|
||||
}
|
||||
}}
|
||||
icon={action.icon}
|
||||
title={action.tooltip || action.title}
|
||||
>
|
||||
<Text size="xs" color={action.color}>
|
||||
|
@ -23,7 +23,7 @@ export function TableHoverCard({
|
||||
return (
|
||||
<HoverCard>
|
||||
<HoverCard.Target>
|
||||
<Group spacing="xs" position="apart">
|
||||
<Group spacing="xs" position="apart" noWrap={true}>
|
||||
{value}
|
||||
<IconInfoCircle size="16" color="blue" />
|
||||
</Group>
|
||||
|
@ -11,7 +11,7 @@ import { YesNoButton } from '../../items/YesNoButton';
|
||||
import { TableColumn } from '../Column';
|
||||
import { TableFilter } from '../Filter';
|
||||
import { InvenTreeTable } from '../InvenTreeTable';
|
||||
import { RowAction } from '../RowActions';
|
||||
import { RowAction, RowDeleteAction, RowEditAction } from '../RowActions';
|
||||
import { TableHoverCard } from '../TableHoverCard';
|
||||
|
||||
export function BomTable({
|
||||
@ -204,14 +204,11 @@ export function BomTable({
|
||||
});
|
||||
}
|
||||
|
||||
actions.push({
|
||||
title: t`Edit`
|
||||
});
|
||||
// TODO: Action on edit
|
||||
actions.push(RowEditAction({}));
|
||||
|
||||
actions.push({
|
||||
title: t`Delete`,
|
||||
color: 'red'
|
||||
});
|
||||
// TODO: Action on delete
|
||||
actions.push(RowDeleteAction({}));
|
||||
|
||||
return actions;
|
||||
},
|
||||
|
@ -11,13 +11,13 @@ import {
|
||||
addAttachment,
|
||||
deleteAttachment,
|
||||
editAttachment
|
||||
} from '../../../functions/forms/AttachmentForms';
|
||||
} from '../../../forms/AttachmentForms';
|
||||
import { useTableRefresh } from '../../../hooks/TableRefresh';
|
||||
import { ApiPaths, apiUrl } from '../../../states/ApiState';
|
||||
import { AttachmentLink } from '../../items/AttachmentLink';
|
||||
import { TableColumn } from '../Column';
|
||||
import { InvenTreeTable } from '../InvenTreeTable';
|
||||
import { RowAction } from '../RowActions';
|
||||
import { RowAction, RowDeleteAction, RowEditAction } from '../RowActions';
|
||||
|
||||
/**
|
||||
* Define set of columns to display for the attachment table
|
||||
@ -113,32 +113,33 @@ export function AttachmentTable({
|
||||
let actions: RowAction[] = [];
|
||||
|
||||
if (allowEdit) {
|
||||
actions.push({
|
||||
title: t`Edit`,
|
||||
onClick: () => {
|
||||
editAttachment({
|
||||
endpoint: endpoint,
|
||||
model: model,
|
||||
pk: record.pk,
|
||||
attachmentType: record.attachment ? 'file' : 'link',
|
||||
callback: refreshTable
|
||||
});
|
||||
}
|
||||
});
|
||||
actions.push(
|
||||
RowEditAction({
|
||||
onClick: () => {
|
||||
editAttachment({
|
||||
endpoint: endpoint,
|
||||
model: model,
|
||||
pk: record.pk,
|
||||
attachmentType: record.attachment ? 'file' : 'link',
|
||||
callback: refreshTable
|
||||
});
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if (allowDelete) {
|
||||
actions.push({
|
||||
title: t`Delete`,
|
||||
color: 'red',
|
||||
onClick: () => {
|
||||
deleteAttachment({
|
||||
endpoint: endpoint,
|
||||
pk: record.pk,
|
||||
callback: refreshTable
|
||||
});
|
||||
}
|
||||
});
|
||||
actions.push(
|
||||
RowDeleteAction({
|
||||
onClick: () => {
|
||||
deleteAttachment({
|
||||
endpoint: endpoint,
|
||||
pk: record.pk,
|
||||
callback: refreshTable
|
||||
});
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
return actions;
|
||||
|
@ -14,6 +14,7 @@ import { Thumbnail } from '../../images/Thumbnail';
|
||||
import { YesNoButton } from '../../items/YesNoButton';
|
||||
import { TableColumn } from '../Column';
|
||||
import { InvenTreeTable } from '../InvenTreeTable';
|
||||
import { RowDeleteAction, RowEditAction } from '../RowActions';
|
||||
|
||||
/**
|
||||
* Construct a table listing parameters for a given part
|
||||
@ -103,44 +104,43 @@ export function PartParameterTable({ partId }: { partId: any }) {
|
||||
|
||||
let actions = [];
|
||||
|
||||
actions.push({
|
||||
title: t`Edit`,
|
||||
onClick: () => {
|
||||
openEditApiForm({
|
||||
name: 'edit-part-parameter',
|
||||
url: ApiPaths.part_parameter_list,
|
||||
pk: record.pk,
|
||||
title: t`Edit Part Parameter`,
|
||||
fields: {
|
||||
part: {
|
||||
hidden: true
|
||||
actions.push(
|
||||
RowEditAction({
|
||||
onClick: () => {
|
||||
openEditApiForm({
|
||||
url: ApiPaths.part_parameter_list,
|
||||
pk: record.pk,
|
||||
title: t`Edit Part Parameter`,
|
||||
fields: {
|
||||
part: {
|
||||
hidden: true
|
||||
},
|
||||
template: {},
|
||||
data: {}
|
||||
},
|
||||
template: {},
|
||||
data: {}
|
||||
},
|
||||
successMessage: t`Part parameter updated`,
|
||||
onFormSuccess: refreshTable
|
||||
});
|
||||
}
|
||||
});
|
||||
successMessage: t`Part parameter updated`,
|
||||
onFormSuccess: refreshTable
|
||||
});
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
actions.push({
|
||||
title: t`Delete`,
|
||||
color: 'red',
|
||||
onClick: () => {
|
||||
openDeleteApiForm({
|
||||
name: 'delete-part-parameter',
|
||||
url: ApiPaths.part_parameter_list,
|
||||
pk: record.pk,
|
||||
title: t`Delete Part Parameter`,
|
||||
successMessage: t`Part parameter deleted`,
|
||||
onFormSuccess: refreshTable,
|
||||
preFormContent: (
|
||||
<Text>{t`Are you sure you want to remove this parameter?`}</Text>
|
||||
)
|
||||
});
|
||||
}
|
||||
});
|
||||
actions.push(
|
||||
RowDeleteAction({
|
||||
onClick: () => {
|
||||
openDeleteApiForm({
|
||||
url: ApiPaths.part_parameter_list,
|
||||
pk: record.pk,
|
||||
title: t`Delete Part Parameter`,
|
||||
successMessage: t`Part parameter deleted`,
|
||||
onFormSuccess: refreshTable,
|
||||
preFormContent: (
|
||||
<Text>{t`Are you sure you want to remove this parameter?`}</Text>
|
||||
)
|
||||
});
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
return actions;
|
||||
},
|
||||
@ -153,7 +153,6 @@ export function PartParameterTable({ partId }: { partId: any }) {
|
||||
}
|
||||
|
||||
openCreateApiForm({
|
||||
name: 'add-part-parameter',
|
||||
url: ApiPaths.part_parameter_list,
|
||||
title: t`Add Part Parameter`,
|
||||
fields: {
|
||||
|
@ -23,16 +23,12 @@ function partTableColumns(): TableColumn[] {
|
||||
noWrap: true,
|
||||
title: t`Part`,
|
||||
render: function (record: any) {
|
||||
// TODO - Link to the part detail page
|
||||
return (
|
||||
<Group spacing="xs" align="left" noWrap={true}>
|
||||
<Thumbnail
|
||||
src={record.thumbnail || record.image}
|
||||
alt={record.name}
|
||||
size={24}
|
||||
/>
|
||||
<Text>{record.full_name}</Text>
|
||||
</Group>
|
||||
<Thumbnail
|
||||
src={record.thumbnail || record.image}
|
||||
alt={record.name}
|
||||
text={record.full_name}
|
||||
/>
|
||||
);
|
||||
}
|
||||
},
|
||||
|
@ -10,6 +10,7 @@ import { ApiPaths, apiUrl } from '../../../states/ApiState';
|
||||
import { Thumbnail } from '../../images/Thumbnail';
|
||||
import { TableColumn } from '../Column';
|
||||
import { InvenTreeTable } from '../InvenTreeTable';
|
||||
import { RowDeleteAction } from '../RowActions';
|
||||
|
||||
/**
|
||||
* Construct a table listing related parts for a given part
|
||||
@ -33,7 +34,6 @@ export function RelatedPartTable({ partId }: { partId: number }): ReactNode {
|
||||
{
|
||||
accessor: 'part',
|
||||
title: t`Part`,
|
||||
noWrap: true,
|
||||
render: (record: any) => {
|
||||
let part = getPart(record);
|
||||
return (
|
||||
@ -63,7 +63,6 @@ export function RelatedPartTable({ partId }: { partId: number }): ReactNode {
|
||||
|
||||
const addRelatedPart = useCallback(() => {
|
||||
openCreateApiForm({
|
||||
name: 'add-related-part',
|
||||
title: t`Add Related Part`,
|
||||
url: ApiPaths.related_part_list,
|
||||
fields: {
|
||||
@ -99,12 +98,9 @@ export function RelatedPartTable({ partId }: { partId: number }): ReactNode {
|
||||
// TODO: Hide if user does not have permission to edit parts
|
||||
const rowActions = useCallback((record: any) => {
|
||||
return [
|
||||
{
|
||||
title: t`Delete`,
|
||||
color: 'red',
|
||||
RowDeleteAction({
|
||||
onClick: () => {
|
||||
openDeleteApiForm({
|
||||
name: 'delete-related-part',
|
||||
url: ApiPaths.related_part_list,
|
||||
pk: record.pk,
|
||||
title: t`Delete Related Part`,
|
||||
@ -115,7 +111,7 @@ export function RelatedPartTable({ partId }: { partId: number }): ReactNode {
|
||||
onFormSuccess: refreshTable
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
];
|
||||
}, []);
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { t } from '@lingui/macro';
|
||||
import { Group, Text } from '@mantine/core';
|
||||
import { useMemo } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
@ -45,10 +44,11 @@ export function PurchaseOrderTable({ params }: { params?: any }) {
|
||||
let supplier = record.supplier_detail ?? {};
|
||||
|
||||
return (
|
||||
<Group spacing="xs" noWrap={true}>
|
||||
<Thumbnail src={supplier?.image} alt={supplier.name} />
|
||||
<Text>{supplier?.name}</Text>
|
||||
</Group>
|
||||
<Thumbnail
|
||||
src={supplier?.image}
|
||||
alt={supplier.name}
|
||||
text={supplier.name}
|
||||
/>
|
||||
);
|
||||
}
|
||||
},
|
||||
|
@ -0,0 +1,258 @@
|
||||
import { t } from '@lingui/macro';
|
||||
import { ActionIcon, Stack, Text, Tooltip } from '@mantine/core';
|
||||
import { IconCirclePlus } from '@tabler/icons-react';
|
||||
import { ReactNode, useCallback, useMemo } from 'react';
|
||||
|
||||
import { supplierPartFields } from '../../../forms/CompanyForms';
|
||||
import {
|
||||
openCreateApiForm,
|
||||
openDeleteApiForm,
|
||||
openEditApiForm
|
||||
} from '../../../functions/forms';
|
||||
import { useTableRefresh } from '../../../hooks/TableRefresh';
|
||||
import { ApiPaths, apiUrl } from '../../../states/ApiState';
|
||||
import { useUserState } from '../../../states/UserState';
|
||||
import { Thumbnail } from '../../images/Thumbnail';
|
||||
import { TableColumn } from '../Column';
|
||||
import { InvenTreeTable } from '../InvenTreeTable';
|
||||
import { RowDeleteAction, RowEditAction } from '../RowActions';
|
||||
import { TableHoverCard } from '../TableHoverCard';
|
||||
|
||||
/*
|
||||
* Construct a table listing supplier parts
|
||||
*/
|
||||
|
||||
export function SupplierPartTable({ params }: { params: any }): ReactNode {
|
||||
const { tableKey, refreshTable } = useTableRefresh('supplierparts');
|
||||
|
||||
const user = useUserState();
|
||||
|
||||
// Construct table columns for this table
|
||||
const tableColumns: TableColumn[] = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
accessor: 'part',
|
||||
title: t`Part`,
|
||||
switchable: 'part' in params,
|
||||
sortable: true,
|
||||
render: (record: any) => {
|
||||
let part = record?.part_detail ?? {};
|
||||
|
||||
return (
|
||||
<Thumbnail src={part?.thumbnail ?? part.image} text={part.name} />
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
accessor: 'supplier',
|
||||
title: t`Supplier`,
|
||||
sortable: true,
|
||||
render: (record: any) => {
|
||||
let supplier = record?.supplier_detail ?? {};
|
||||
|
||||
return (
|
||||
<Thumbnail
|
||||
src={supplier?.thumbnail ?? supplier.image}
|
||||
text={supplier.name}
|
||||
/>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
accessor: 'SKU',
|
||||
title: t`Supplier Part`,
|
||||
sortable: true
|
||||
},
|
||||
{
|
||||
accessor: 'description',
|
||||
title: t`Description`,
|
||||
sortable: false,
|
||||
switchable: true
|
||||
},
|
||||
{
|
||||
accessor: 'manufacturer',
|
||||
switchable: true,
|
||||
sortable: true,
|
||||
title: t`Manufacturer`,
|
||||
render: (record: any) => {
|
||||
let manufacturer = record?.manufacturer_detail ?? {};
|
||||
|
||||
return (
|
||||
<Thumbnail
|
||||
src={manufacturer?.thumbnail ?? manufacturer.image}
|
||||
text={manufacturer.name}
|
||||
/>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
accessor: 'MPN',
|
||||
switchable: true,
|
||||
sortable: true,
|
||||
title: t`MPN`,
|
||||
render: (record: any) => record?.manufacturer_part_detail?.MPN
|
||||
},
|
||||
{
|
||||
accessor: 'in_stock',
|
||||
title: t`In Stock`,
|
||||
sortable: true,
|
||||
switchable: true
|
||||
},
|
||||
{
|
||||
accessor: 'packaging',
|
||||
title: t`Packaging`,
|
||||
sortable: true,
|
||||
switchable: true
|
||||
},
|
||||
{
|
||||
accessor: 'pack_quantity',
|
||||
title: t`Pack Quantity`,
|
||||
sortable: true,
|
||||
switchable: true,
|
||||
render: (record: any) => {
|
||||
let part = record?.part_detail ?? {};
|
||||
|
||||
let extra = [];
|
||||
|
||||
if (part.units) {
|
||||
extra.push(
|
||||
<Text>
|
||||
{t`Base units`} : {part.units}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<TableHoverCard
|
||||
value={record.pack_quantity}
|
||||
extra={extra.length > 0 && <Stack spacing="xs">{extra}</Stack>}
|
||||
title={t`Pack Quantity`}
|
||||
/>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
accessor: 'link',
|
||||
title: t`Link`,
|
||||
sortable: false,
|
||||
switchable: true
|
||||
// TODO: custom link renderer?
|
||||
},
|
||||
{
|
||||
accessor: 'note',
|
||||
title: t`Notes`,
|
||||
sortable: false,
|
||||
switchable: true
|
||||
},
|
||||
{
|
||||
accessor: 'available',
|
||||
title: t`Availability`,
|
||||
sortable: true,
|
||||
switchable: true,
|
||||
render: (record: any) => {
|
||||
let extra = [];
|
||||
|
||||
if (record.availablility_updated) {
|
||||
extra.push(
|
||||
<Text>
|
||||
{t`Updated`} : {record.availablility_updated}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<TableHoverCard
|
||||
value={record.available}
|
||||
extra={extra.length > 0 && <Stack spacing="xs">{extra}</Stack>}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
];
|
||||
}, [params]);
|
||||
|
||||
const addSupplierPart = useCallback(() => {
|
||||
let fields = supplierPartFields();
|
||||
|
||||
fields.part.value = params?.part;
|
||||
fields.supplier.value = params?.supplier;
|
||||
|
||||
openCreateApiForm({
|
||||
url: ApiPaths.supplier_part_list,
|
||||
title: t`Add Supplier Part`,
|
||||
fields: fields,
|
||||
onFormSuccess: refreshTable,
|
||||
successMessage: t`Supplier part created`
|
||||
});
|
||||
}, [params]);
|
||||
|
||||
// Table actions
|
||||
const tableActions = useMemo(() => {
|
||||
// TODO: Hide actions based on user permissions
|
||||
|
||||
return [
|
||||
// TODO: Refactor this component out to something reusable
|
||||
<Tooltip label={t`Add supplier part`}>
|
||||
<ActionIcon radius="sm" onClick={addSupplierPart}>
|
||||
<IconCirclePlus color="green" />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
];
|
||||
}, [user]);
|
||||
|
||||
// Row action callback
|
||||
const rowActions = useCallback(
|
||||
(record: any) => {
|
||||
// TODO: Adjust actions based on user permissions
|
||||
return [
|
||||
RowEditAction({
|
||||
onClick: () => {
|
||||
record.pk &&
|
||||
openEditApiForm({
|
||||
url: ApiPaths.supplier_part_list,
|
||||
pk: record.pk,
|
||||
title: t`Edit Supplier Part`,
|
||||
fields: supplierPartFields(),
|
||||
onFormSuccess: refreshTable,
|
||||
successMessage: t`Supplier part updated`
|
||||
});
|
||||
}
|
||||
}),
|
||||
RowDeleteAction({
|
||||
onClick: () => {
|
||||
record.pk &&
|
||||
openDeleteApiForm({
|
||||
url: ApiPaths.supplier_part_list,
|
||||
pk: record.pk,
|
||||
title: t`Delete Supplier Part`,
|
||||
successMessage: t`Supplier part deleted`,
|
||||
onFormSuccess: refreshTable,
|
||||
preFormContent: (
|
||||
<Text>{t`Are you sure you want to remove this supplier part?`}</Text>
|
||||
)
|
||||
});
|
||||
}
|
||||
})
|
||||
];
|
||||
},
|
||||
[user]
|
||||
);
|
||||
|
||||
return (
|
||||
<InvenTreeTable
|
||||
url={apiUrl(ApiPaths.supplier_part_list)}
|
||||
tableKey={tableKey}
|
||||
columns={tableColumns}
|
||||
props={{
|
||||
params: {
|
||||
...params,
|
||||
part_detail: true,
|
||||
supplier_detail: true,
|
||||
manufacturer_detail: true
|
||||
},
|
||||
rowActions: rowActions,
|
||||
customActionGroups: tableActions
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
@ -1,5 +1,4 @@
|
||||
import { t } from '@lingui/macro';
|
||||
import { Group, Text } from '@mantine/core';
|
||||
import { useMemo } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
@ -41,10 +40,11 @@ export function ReturnOrderTable({ params }: { params?: any }) {
|
||||
let customer = record.customer_detail ?? {};
|
||||
|
||||
return (
|
||||
<Group spacing="xs" noWrap={true}>
|
||||
<Thumbnail src={customer?.image} alt={customer.name} />
|
||||
<Text>{customer?.name}</Text>
|
||||
</Group>
|
||||
<Thumbnail
|
||||
src={customer?.image}
|
||||
alt={customer.name}
|
||||
text={customer.name}
|
||||
/>
|
||||
);
|
||||
}
|
||||
},
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { t } from '@lingui/macro';
|
||||
import { Group, Text } from '@mantine/core';
|
||||
import { useMemo } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
@ -42,10 +41,11 @@ export function SalesOrderTable({ params }: { params?: any }) {
|
||||
let customer = record.customer_detail ?? {};
|
||||
|
||||
return (
|
||||
<Group spacing="xs" noWrap={true}>
|
||||
<Thumbnail src={customer?.image} alt={customer.name} />
|
||||
<Text>{customer?.name}</Text>
|
||||
</Group>
|
||||
<Thumbnail
|
||||
src={customer?.image}
|
||||
alt={customer.name}
|
||||
text={customer.name}
|
||||
/>
|
||||
);
|
||||
}
|
||||
},
|
||||
|
@ -12,7 +12,7 @@ import { useTableRefresh } from '../../../hooks/TableRefresh';
|
||||
import { ApiPaths, apiUrl } from '../../../states/ApiState';
|
||||
import { TableColumn } from '../Column';
|
||||
import { InvenTreeTable } from '../InvenTreeTable';
|
||||
import { RowAction } from '../RowActions';
|
||||
import { RowAction, RowDeleteAction, RowEditAction } from '../RowActions';
|
||||
|
||||
/**
|
||||
* Table for displaying list of custom physical units
|
||||
@ -45,11 +45,9 @@ export function CustomUnitsTable() {
|
||||
|
||||
const rowActions = useCallback((record: any): RowAction[] => {
|
||||
return [
|
||||
{
|
||||
title: t`Edit`,
|
||||
RowEditAction({
|
||||
onClick: () => {
|
||||
openEditApiForm({
|
||||
name: 'edit-custom-unit',
|
||||
url: ApiPaths.custom_unit_list,
|
||||
pk: record.pk,
|
||||
title: t`Edit custom unit`,
|
||||
@ -62,12 +60,10 @@ export function CustomUnitsTable() {
|
||||
successMessage: t`Custom unit updated`
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
title: t`Delete`,
|
||||
}),
|
||||
RowDeleteAction({
|
||||
onClick: () => {
|
||||
openDeleteApiForm({
|
||||
name: 'delete-custom-unit',
|
||||
url: ApiPaths.custom_unit_list,
|
||||
pk: record.pk,
|
||||
title: t`Delete custom unit`,
|
||||
@ -78,13 +74,12 @@ export function CustomUnitsTable() {
|
||||
)
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
];
|
||||
}, []);
|
||||
|
||||
const addCustomUnit = useCallback(() => {
|
||||
openCreateApiForm({
|
||||
name: 'add-custom-unit',
|
||||
url: ApiPaths.custom_unit_list,
|
||||
title: t`Add custom unit`,
|
||||
fields: {
|
||||
|
@ -12,7 +12,7 @@ import { useTableRefresh } from '../../../hooks/TableRefresh';
|
||||
import { ApiPaths, apiUrl } from '../../../states/ApiState';
|
||||
import { TableColumn } from '../Column';
|
||||
import { InvenTreeTable } from '../InvenTreeTable';
|
||||
import { RowAction } from '../RowActions';
|
||||
import { RowAction, RowDeleteAction, RowEditAction } from '../RowActions';
|
||||
|
||||
/**
|
||||
* Table for displaying list of project codes
|
||||
@ -37,11 +37,9 @@ export function ProjectCodeTable() {
|
||||
|
||||
const rowActions = useCallback((record: any): RowAction[] => {
|
||||
return [
|
||||
{
|
||||
title: t`Edit`,
|
||||
RowEditAction({
|
||||
onClick: () => {
|
||||
openEditApiForm({
|
||||
name: 'edit-project-code',
|
||||
url: ApiPaths.project_code_list,
|
||||
pk: record.pk,
|
||||
title: t`Edit project code`,
|
||||
@ -53,13 +51,10 @@ export function ProjectCodeTable() {
|
||||
successMessage: t`Project code updated`
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
title: t`Delete`,
|
||||
color: 'red',
|
||||
}),
|
||||
RowDeleteAction({
|
||||
onClick: () => {
|
||||
openDeleteApiForm({
|
||||
name: 'delete-project-code',
|
||||
url: ApiPaths.project_code_list,
|
||||
pk: record.pk,
|
||||
title: t`Delete project code`,
|
||||
@ -70,13 +65,12 @@ export function ProjectCodeTable() {
|
||||
)
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
];
|
||||
}, []);
|
||||
|
||||
const addProjectCode = useCallback(() => {
|
||||
openCreateApiForm({
|
||||
name: 'add-project-code',
|
||||
url: ApiPaths.project_code_list,
|
||||
title: t`Add project code`,
|
||||
fields: {
|
||||
|
@ -3,7 +3,6 @@ import { Group, Stack, Text } from '@mantine/core';
|
||||
import { ReactNode, useMemo } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { notYetImplemented } from '../../../functions/notifications';
|
||||
import { useTableRefresh } from '../../../hooks/TableRefresh';
|
||||
import { ApiPaths, apiUrl } from '../../../states/ApiState';
|
||||
import { Thumbnail } from '../../images/Thumbnail';
|
||||
@ -219,13 +218,7 @@ export function StockItemTable({ params = {} }: { params?: any }) {
|
||||
function stockItemRowActions(record: any): RowAction[] {
|
||||
let actions: RowAction[] = [];
|
||||
|
||||
actions.push({
|
||||
title: t`Edit`,
|
||||
onClick: () => {
|
||||
notYetImplemented();
|
||||
}
|
||||
});
|
||||
|
||||
// TODO: Custom row actions for stock table
|
||||
return actions;
|
||||
}
|
||||
|
||||
|
@ -1,13 +1,13 @@
|
||||
import { t } from '@lingui/macro';
|
||||
import { Text } from '@mantine/core';
|
||||
|
||||
import { ApiFormFieldSet } from '../../components/forms/fields/ApiFormField';
|
||||
import { ApiPaths } from '../../states/ApiState';
|
||||
import { ApiFormFieldSet } from '../components/forms/fields/ApiFormField';
|
||||
import {
|
||||
openCreateApiForm,
|
||||
openDeleteApiForm,
|
||||
openEditApiForm
|
||||
} from '../forms';
|
||||
} from '../functions/forms';
|
||||
import { ApiPaths } from '../states/ApiState';
|
||||
|
||||
export function attachmentFields(editing: boolean): ApiFormFieldSet {
|
||||
let fields: ApiFormFieldSet = {
|
||||
@ -59,7 +59,6 @@ export function addAttachment({
|
||||
let message = attachmentType === 'file' ? t`File added` : t`Link added`;
|
||||
|
||||
openCreateApiForm({
|
||||
name: 'attachment-add',
|
||||
title: title,
|
||||
url: endpoint,
|
||||
successMessage: message,
|
||||
@ -102,7 +101,6 @@ export function editAttachment({
|
||||
let message = attachmentType === 'file' ? t`File updated` : t`Link updated`;
|
||||
|
||||
openEditApiForm({
|
||||
name: 'attachment-edit',
|
||||
title: title,
|
||||
url: endpoint,
|
||||
pk: pk,
|
||||
@ -124,7 +122,6 @@ export function deleteAttachment({
|
||||
openDeleteApiForm({
|
||||
url: endpoint,
|
||||
pk: pk,
|
||||
name: 'attachment-edit',
|
||||
title: t`Delete Attachment`,
|
||||
successMessage: t`Attachment deleted`,
|
||||
onFormSuccess: callback,
|
106
src/frontend/src/forms/CompanyForms.tsx
Normal file
106
src/frontend/src/forms/CompanyForms.tsx
Normal file
@ -0,0 +1,106 @@
|
||||
import { t } from '@lingui/macro';
|
||||
import {
|
||||
IconAt,
|
||||
IconCurrencyDollar,
|
||||
IconGlobe,
|
||||
IconHash,
|
||||
IconLink,
|
||||
IconNote,
|
||||
IconPackage,
|
||||
IconPhone
|
||||
} from '@tabler/icons-react';
|
||||
|
||||
import {
|
||||
ApiFormData,
|
||||
ApiFormFieldSet
|
||||
} from '../components/forms/fields/ApiFormField';
|
||||
import { openEditApiForm } from '../functions/forms';
|
||||
import { ApiPaths } from '../states/ApiState';
|
||||
|
||||
/**
|
||||
* Field set for SupplierPart instance
|
||||
*/
|
||||
export function supplierPartFields(): ApiFormFieldSet {
|
||||
return {
|
||||
part: {
|
||||
filters: {
|
||||
purchaseable: true
|
||||
}
|
||||
},
|
||||
manufacturer_part: {
|
||||
filters: {
|
||||
part_detail: true,
|
||||
manufacturer_detail: true
|
||||
},
|
||||
adjustFilters: (filters: any, form: ApiFormData) => {
|
||||
let part = form.values.part;
|
||||
|
||||
if (part) {
|
||||
filters.part = part;
|
||||
}
|
||||
|
||||
return filters;
|
||||
}
|
||||
},
|
||||
supplier: {},
|
||||
SKU: {
|
||||
icon: <IconHash />
|
||||
},
|
||||
description: {},
|
||||
link: {
|
||||
icon: <IconLink />
|
||||
},
|
||||
note: {
|
||||
icon: <IconNote />
|
||||
},
|
||||
pack_quantity: {},
|
||||
packaging: {
|
||||
icon: <IconPackage />
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Field set for editing a company instance
|
||||
*/
|
||||
export function companyFields(): ApiFormFieldSet {
|
||||
return {
|
||||
name: {},
|
||||
description: {},
|
||||
website: {
|
||||
icon: <IconGlobe />
|
||||
},
|
||||
currency: {
|
||||
icon: <IconCurrencyDollar />
|
||||
},
|
||||
phone: {
|
||||
icon: <IconPhone />
|
||||
},
|
||||
email: {
|
||||
icon: <IconAt />
|
||||
},
|
||||
is_supplier: {},
|
||||
is_manufacturer: {},
|
||||
is_customer: {}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit a company instance
|
||||
*/
|
||||
export function editCompany({
|
||||
pk,
|
||||
callback
|
||||
}: {
|
||||
pk: number;
|
||||
callback?: () => void;
|
||||
}) {
|
||||
openEditApiForm({
|
||||
title: t`Edit Company`,
|
||||
url: ApiPaths.company_list,
|
||||
pk: pk,
|
||||
fields: companyFields(),
|
||||
successMessage: t`Company updated`,
|
||||
onFormSuccess: callback
|
||||
});
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
import { t } from '@lingui/macro';
|
||||
|
||||
import { ApiFormFieldSet } from '../../components/forms/fields/ApiFormField';
|
||||
import { ApiPaths } from '../../states/ApiState';
|
||||
import { openCreateApiForm, openEditApiForm } from '../forms';
|
||||
import { ApiFormFieldSet } from '../components/forms/fields/ApiFormField';
|
||||
import { openCreateApiForm, openEditApiForm } from '../functions/forms';
|
||||
import { ApiPaths } from '../states/ApiState';
|
||||
|
||||
/**
|
||||
* Construct a set of fields for creating / editing a Part instance
|
||||
@ -70,7 +70,6 @@ export function partFields({
|
||||
*/
|
||||
export function createPart() {
|
||||
openCreateApiForm({
|
||||
name: 'part-create',
|
||||
title: t`Create Part`,
|
||||
url: ApiPaths.part_list,
|
||||
successMessage: t`Part created`,
|
||||
@ -90,7 +89,6 @@ export function editPart({
|
||||
callback?: () => void;
|
||||
}) {
|
||||
openEditApiForm({
|
||||
name: 'part-edit',
|
||||
title: t`Edit Part`,
|
||||
url: ApiPaths.part_list,
|
||||
pk: part_id,
|
@ -4,9 +4,9 @@ import {
|
||||
ApiFormChangeCallback,
|
||||
ApiFormData,
|
||||
ApiFormFieldSet
|
||||
} from '../../components/forms/fields/ApiFormField';
|
||||
import { ApiPaths } from '../../states/ApiState';
|
||||
import { openCreateApiForm, openEditApiForm } from '../forms';
|
||||
} from '../components/forms/fields/ApiFormField';
|
||||
import { openCreateApiForm, openEditApiForm } from '../functions/forms';
|
||||
import { ApiPaths } from '../states/ApiState';
|
||||
|
||||
/**
|
||||
* Construct a set of fields for creating / editing a StockItem instance
|
||||
@ -30,7 +30,6 @@ export function stockFields({
|
||||
},
|
||||
supplier_part: {
|
||||
// TODO: icon
|
||||
// TODO: implement adjustFilters
|
||||
filters: {
|
||||
part_detail: true,
|
||||
supplier_detail: true
|
||||
@ -107,7 +106,6 @@ export function stockFields({
|
||||
*/
|
||||
export function createStockItem() {
|
||||
openCreateApiForm({
|
||||
name: 'stockitem-create',
|
||||
url: ApiPaths.stock_item_list,
|
||||
fields: stockFields({ create: true }),
|
||||
title: t`Create Stock Item`
|
||||
@ -126,7 +124,6 @@ export function editStockItem({
|
||||
callback?: () => void;
|
||||
}) {
|
||||
openEditApiForm({
|
||||
name: 'stockitem-edit',
|
||||
url: ApiPaths.stock_item_list,
|
||||
pk: item_id,
|
||||
fields: stockFields({ create: false }),
|
@ -115,10 +115,12 @@ export function openModalApiForm(props: ApiFormProps) {
|
||||
}
|
||||
|
||||
// Generate a random modal ID for controller
|
||||
let modalId: string = `modal-${props.title}-` + generateUniqueId();
|
||||
let modalId: string =
|
||||
`modal-${props.title}-${props.url}-${props.method}` +
|
||||
generateUniqueId();
|
||||
|
||||
modals.open({
|
||||
title: <StylishText>{props.title}</StylishText>,
|
||||
title: <StylishText size="xl">{props.title}</StylishText>,
|
||||
modalId: modalId,
|
||||
size: 'xl',
|
||||
onClose: () => {
|
||||
|
@ -1,57 +0,0 @@
|
||||
import { t } from '@lingui/macro';
|
||||
import {
|
||||
IconAt,
|
||||
IconCurrencyDollar,
|
||||
IconGlobe,
|
||||
IconPhone
|
||||
} from '@tabler/icons-react';
|
||||
|
||||
import { ApiFormFieldSet } from '../../components/forms/fields/ApiFormField';
|
||||
import { ApiPaths } from '../../states/ApiState';
|
||||
import { openEditApiForm } from '../forms';
|
||||
|
||||
/**
|
||||
* Field set for editing a company instance
|
||||
*/
|
||||
export function companyFields(): ApiFormFieldSet {
|
||||
return {
|
||||
name: {},
|
||||
description: {},
|
||||
website: {
|
||||
icon: <IconGlobe />
|
||||
},
|
||||
currency: {
|
||||
icon: <IconCurrencyDollar />
|
||||
},
|
||||
phone: {
|
||||
icon: <IconPhone />
|
||||
},
|
||||
email: {
|
||||
icon: <IconAt />
|
||||
},
|
||||
is_supplier: {},
|
||||
is_manufacturer: {},
|
||||
is_customer: {}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit a company instance
|
||||
*/
|
||||
export function editCompany({
|
||||
pk,
|
||||
callback
|
||||
}: {
|
||||
pk: number;
|
||||
callback?: () => void;
|
||||
}) {
|
||||
openEditApiForm({
|
||||
name: 'company-edit',
|
||||
title: t`Edit Company`,
|
||||
url: ApiPaths.company_list,
|
||||
pk: pk,
|
||||
fields: companyFields(),
|
||||
successMessage: t`Company updated`,
|
||||
onFormSuccess: callback
|
||||
});
|
||||
}
|
@ -9,13 +9,13 @@ import { PlaceholderPill } from '../../components/items/Placeholder';
|
||||
import { StylishText } from '../../components/items/StylishText';
|
||||
import { ModelType } from '../../components/render/ModelType';
|
||||
import { StatusRenderer } from '../../components/renderers/StatusRenderer';
|
||||
import { openCreateApiForm, openEditApiForm } from '../../functions/forms';
|
||||
import {
|
||||
createPart,
|
||||
editPart,
|
||||
partCategoryFields
|
||||
} from '../../functions/forms/PartForms';
|
||||
import { createStockItem } from '../../functions/forms/StockForms';
|
||||
} from '../../forms/PartForms';
|
||||
import { createStockItem } from '../../forms/StockForms';
|
||||
import { openCreateApiForm, openEditApiForm } from '../../functions/forms';
|
||||
import { ApiPaths } from '../../states/ApiState';
|
||||
|
||||
// Generate some example forms using the modal API forms interface
|
||||
@ -23,7 +23,6 @@ function ApiFormsPlayground() {
|
||||
let fields = partCategoryFields({});
|
||||
|
||||
const editCategoryForm: ApiFormProps = {
|
||||
name: 'partcategory',
|
||||
url: ApiPaths.category_list,
|
||||
pk: 2,
|
||||
title: 'Edit Category',
|
||||
@ -31,7 +30,6 @@ function ApiFormsPlayground() {
|
||||
};
|
||||
|
||||
const createAttachmentForm: ApiFormProps = {
|
||||
name: 'createattachment',
|
||||
url: ApiPaths.part_attachment_list,
|
||||
title: 'Create Attachment',
|
||||
successMessage: 'Attachment uploaded',
|
||||
|
@ -31,7 +31,7 @@ import { ReturnOrderTable } from '../../components/tables/sales/ReturnOrderTable
|
||||
import { SalesOrderTable } from '../../components/tables/sales/SalesOrderTable';
|
||||
import { StockItemTable } from '../../components/tables/stock/StockItemTable';
|
||||
import { NotesEditor } from '../../components/widgets/MarkdownEditor';
|
||||
import { editCompany } from '../../functions/forms/CompanyForms';
|
||||
import { editCompany } from '../../forms/CompanyForms';
|
||||
import { useInstance } from '../../hooks/UseInstance';
|
||||
import { ApiPaths, apiUrl } from '../../states/ApiState';
|
||||
import { useUserState } from '../../states/UserState';
|
||||
|
@ -1,7 +1,9 @@
|
||||
import { t } from '@lingui/macro';
|
||||
import { Group, LoadingOverlay, Stack, Text } from '@mantine/core';
|
||||
import {
|
||||
IconBookmarks,
|
||||
IconBuilding,
|
||||
IconBuildingFactory2,
|
||||
IconCalendarStats,
|
||||
IconClipboardList,
|
||||
IconCopy,
|
||||
@ -44,10 +46,11 @@ import { AttachmentTable } from '../../components/tables/general/AttachmentTable
|
||||
import { PartParameterTable } from '../../components/tables/part/PartParameterTable';
|
||||
import { PartVariantTable } from '../../components/tables/part/PartVariantTable';
|
||||
import { RelatedPartTable } from '../../components/tables/part/RelatedPartTable';
|
||||
import { SupplierPartTable } from '../../components/tables/purchasing/SupplierPartTable';
|
||||
import { SalesOrderTable } from '../../components/tables/sales/SalesOrderTable';
|
||||
import { StockItemTable } from '../../components/tables/stock/StockItemTable';
|
||||
import { NotesEditor } from '../../components/widgets/MarkdownEditor';
|
||||
import { editPart } from '../../functions/forms/PartForms';
|
||||
import { editPart } from '../../forms/PartForms';
|
||||
import { useInstance } from '../../hooks/UseInstance';
|
||||
import { ApiPaths, apiUrl } from '../../states/ApiState';
|
||||
import { useUserState } from '../../states/UserState';
|
||||
@ -108,6 +111,12 @@ export default function PartDetail() {
|
||||
hidden: !part.is_template,
|
||||
content: <PartVariantTable partId={String(id)} />
|
||||
},
|
||||
{
|
||||
name: 'allocations',
|
||||
label: t`Allocations`,
|
||||
icon: <IconBookmarks />,
|
||||
hidden: !part.component && !part.salable
|
||||
},
|
||||
{
|
||||
name: 'bom',
|
||||
label: t`Bill of Materials`,
|
||||
@ -119,7 +128,7 @@ export default function PartDetail() {
|
||||
name: 'builds',
|
||||
label: t`Build Orders`,
|
||||
icon: <IconTools />,
|
||||
hidden: !part.assembly && !part.component,
|
||||
hidden: !part.assembly,
|
||||
content: (
|
||||
<BuildOrderTable
|
||||
params={{
|
||||
@ -141,11 +150,24 @@ export default function PartDetail() {
|
||||
label: t`Pricing`,
|
||||
icon: <IconCurrencyDollar />
|
||||
},
|
||||
{
|
||||
name: 'manufacturers',
|
||||
label: t`Manufacturers`,
|
||||
icon: <IconBuildingFactory2 />,
|
||||
hidden: !part.purchaseable
|
||||
},
|
||||
{
|
||||
name: 'suppliers',
|
||||
label: t`Suppliers`,
|
||||
icon: <IconBuilding />,
|
||||
hidden: !part.purchaseable
|
||||
hidden: !part.purchaseable,
|
||||
content: part.pk && (
|
||||
<SupplierPartTable
|
||||
params={{
|
||||
part: part.pk ?? -1
|
||||
}}
|
||||
/>
|
||||
)
|
||||
},
|
||||
{
|
||||
name: 'purchase_orders',
|
||||
|
@ -35,7 +35,7 @@ import { PanelGroup, PanelType } from '../../components/nav/PanelGroup';
|
||||
import { StockLocationTree } from '../../components/nav/StockLocationTree';
|
||||
import { AttachmentTable } from '../../components/tables/general/AttachmentTable';
|
||||
import { NotesEditor } from '../../components/widgets/MarkdownEditor';
|
||||
import { editStockItem } from '../../functions/forms/StockForms';
|
||||
import { editStockItem } from '../../forms/StockForms';
|
||||
import { useInstance } from '../../hooks/UseInstance';
|
||||
import { ApiPaths, apiUrl } from '../../states/ApiState';
|
||||
import { useUserState } from '../../states/UserState';
|
||||
|
Loading…
Reference in New Issue
Block a user