mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
[React] Build improvements (#5859)
* Add filters for BuildTable * Add inline renderer for ProjectCode * Pass form response data to onFormSuccess callback * Create new build from build index * Edit existing build order * Add projectcode to list * Bug fixess
This commit is contained in:
parent
1640f605a0
commit
fb7020a85a
@ -49,7 +49,7 @@ export interface ApiFormProps {
|
||||
postFormContent?: JSX.Element | (() => JSX.Element);
|
||||
successMessage?: string;
|
||||
onClose?: () => void;
|
||||
onFormSuccess?: () => void;
|
||||
onFormSuccess?: (data: any) => void;
|
||||
onFormError?: () => void;
|
||||
}
|
||||
|
||||
@ -184,7 +184,7 @@ export function ApiForm({
|
||||
|
||||
// Optionally call the onFormSuccess callback
|
||||
if (props.onFormSuccess) {
|
||||
props.onFormSuccess();
|
||||
props.onFormSuccess(response.data);
|
||||
}
|
||||
|
||||
// Optionally show a success message
|
||||
|
@ -92,16 +92,16 @@ export function BarcodeActionDropdown({
|
||||
// Common action button for viewing a barcode
|
||||
export function ViewBarcodeAction({
|
||||
disabled = false,
|
||||
callback
|
||||
onClick
|
||||
}: {
|
||||
disabled?: boolean;
|
||||
callback?: () => void;
|
||||
onClick?: () => void;
|
||||
}): ActionDropdownItem {
|
||||
return {
|
||||
icon: <IconQrcode />,
|
||||
name: t`View`,
|
||||
tooltip: t`View barcode`,
|
||||
onClick: callback,
|
||||
onClick: onClick,
|
||||
disabled: disabled
|
||||
};
|
||||
}
|
||||
@ -109,16 +109,16 @@ export function ViewBarcodeAction({
|
||||
// Common action button for linking a custom barcode
|
||||
export function LinkBarcodeAction({
|
||||
disabled = false,
|
||||
callback
|
||||
onClick
|
||||
}: {
|
||||
disabled?: boolean;
|
||||
callback?: () => void;
|
||||
onClick?: () => void;
|
||||
}): ActionDropdownItem {
|
||||
return {
|
||||
icon: <IconLink />,
|
||||
name: t`Link Barcode`,
|
||||
tooltip: t`Link custom barcode`,
|
||||
onClick: callback,
|
||||
onClick: onClick,
|
||||
disabled: disabled
|
||||
};
|
||||
}
|
||||
@ -126,16 +126,16 @@ export function LinkBarcodeAction({
|
||||
// Common action button for un-linking a custom barcode
|
||||
export function UnlinkBarcodeAction({
|
||||
disabled = false,
|
||||
callback
|
||||
onClick
|
||||
}: {
|
||||
disabled?: boolean;
|
||||
callback?: () => void;
|
||||
onClick?: () => void;
|
||||
}): ActionDropdownItem {
|
||||
return {
|
||||
icon: <IconUnlink />,
|
||||
name: t`Unlink Barcode`,
|
||||
tooltip: t`Unlink custom barcode`,
|
||||
onClick: callback,
|
||||
onClick: onClick,
|
||||
disabled: disabled
|
||||
};
|
||||
}
|
||||
@ -144,17 +144,17 @@ export function UnlinkBarcodeAction({
|
||||
export function EditItemAction({
|
||||
disabled = false,
|
||||
tooltip,
|
||||
callback
|
||||
onClick
|
||||
}: {
|
||||
disabled?: boolean;
|
||||
tooltip?: string;
|
||||
callback?: () => void;
|
||||
onClick?: () => void;
|
||||
}): ActionDropdownItem {
|
||||
return {
|
||||
icon: <IconEdit color="blue" />,
|
||||
name: t`Edit`,
|
||||
tooltip: tooltip ?? `Edit item`,
|
||||
onClick: callback,
|
||||
onClick: onClick,
|
||||
disabled: disabled
|
||||
};
|
||||
}
|
||||
@ -163,17 +163,17 @@ export function EditItemAction({
|
||||
export function DeleteItemAction({
|
||||
disabled = false,
|
||||
tooltip,
|
||||
callback
|
||||
onClick
|
||||
}: {
|
||||
disabled?: boolean;
|
||||
tooltip?: string;
|
||||
callback?: () => void;
|
||||
onClick?: () => void;
|
||||
}): ActionDropdownItem {
|
||||
return {
|
||||
icon: <IconTrash color="red" />,
|
||||
name: t`Delete`,
|
||||
tooltip: tooltip ?? t`Delete item`,
|
||||
onClick: callback,
|
||||
onClick: onClick,
|
||||
disabled: disabled
|
||||
};
|
||||
}
|
||||
@ -182,17 +182,17 @@ export function DeleteItemAction({
|
||||
export function DuplicateItemAction({
|
||||
disabled = false,
|
||||
tooltip,
|
||||
callback
|
||||
onClick
|
||||
}: {
|
||||
disabled?: boolean;
|
||||
tooltip?: string;
|
||||
callback?: () => void;
|
||||
onClick?: () => void;
|
||||
}): ActionDropdownItem {
|
||||
return {
|
||||
icon: <IconCopy color="green" />,
|
||||
name: t`Duplicate`,
|
||||
tooltip: tooltip ?? t`Duplicate item`,
|
||||
onClick: callback,
|
||||
onClick: onClick,
|
||||
disabled: disabled
|
||||
};
|
||||
}
|
||||
|
14
src/frontend/src/components/render/Generic.tsx
Normal file
14
src/frontend/src/components/render/Generic.tsx
Normal file
@ -0,0 +1,14 @@
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
import { RenderInlineModel } from './Instance';
|
||||
|
||||
export function RenderProjectCode({ instance }: { instance: any }): ReactNode {
|
||||
return (
|
||||
instance && (
|
||||
<RenderInlineModel
|
||||
primary={instance.code}
|
||||
secondary={instance.description}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
@ -12,6 +12,7 @@ import {
|
||||
RenderManufacturerPart,
|
||||
RenderSupplierPart
|
||||
} from './Company';
|
||||
import { RenderProjectCode } from './Generic';
|
||||
import { ModelType } from './ModelType';
|
||||
import {
|
||||
RenderPurchaseOrder,
|
||||
@ -47,6 +48,7 @@ const RendererLookup: EnumDictionary<
|
||||
[ModelType.part]: RenderPart,
|
||||
[ModelType.partcategory]: RenderPartCategory,
|
||||
[ModelType.partparametertemplate]: RenderPartParameterTemplate,
|
||||
[ModelType.projectcode]: RenderProjectCode,
|
||||
[ModelType.purchaseorder]: RenderPurchaseOrder,
|
||||
[ModelType.purchaseorderline]: RenderPurchaseOrder,
|
||||
[ModelType.returnorder]: RenderReturnOrder,
|
||||
|
@ -6,6 +6,7 @@ export enum ModelType {
|
||||
manufacturerpart = 'manufacturerpart',
|
||||
partcategory = 'partcategory',
|
||||
partparametertemplate = 'partparametertemplate',
|
||||
projectcode = 'projectcode',
|
||||
stockitem = 'stockitem',
|
||||
stocklocation = 'stocklocation',
|
||||
stockhistory = 'stockhistory',
|
||||
@ -92,6 +93,12 @@ export const ModelInformationDict: ModelDictory = {
|
||||
url_overview: '/company',
|
||||
url_detail: '/company/:pk/'
|
||||
},
|
||||
projectcode: {
|
||||
label: t`Project Code`,
|
||||
label_multiple: t`Project Codes`,
|
||||
url_overview: '/project-code',
|
||||
url_detail: '/project-code/:pk/'
|
||||
},
|
||||
purchaseorder: {
|
||||
label: t`Purchase Order`,
|
||||
label_multiple: t`Purchase Orders`,
|
||||
|
@ -1,10 +1,13 @@
|
||||
import { t } from '@lingui/macro';
|
||||
import { Text } from '@mantine/core';
|
||||
import { useMemo } from 'react';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { buildOrderFields } from '../../../forms/BuildForms';
|
||||
import { openCreateApiForm } from '../../../functions/forms';
|
||||
import { useTableRefresh } from '../../../hooks/TableRefresh';
|
||||
import { ApiPaths, apiUrl } from '../../../states/ApiState';
|
||||
import { AddItemButton } from '../../buttons/AddItemButton';
|
||||
import { ThumbnailHoverCard } from '../../images/Thumbnail';
|
||||
import { ProgressBar } from '../../items/ProgressBar';
|
||||
import { ModelType } from '../../render/ModelType';
|
||||
@ -50,16 +53,11 @@ function buildOrderTableColumns(): TableColumn[] {
|
||||
sortable: false,
|
||||
title: t`Description`
|
||||
},
|
||||
{
|
||||
accessor: 'quantity',
|
||||
sortable: true,
|
||||
title: t`Quantity`,
|
||||
switchable: false
|
||||
},
|
||||
{
|
||||
accessor: 'completed',
|
||||
sortable: true,
|
||||
title: t`Completed`,
|
||||
switchable: false,
|
||||
title: t`Progress`,
|
||||
render: (record: any) => (
|
||||
<ProgressBar
|
||||
progressLabel={true}
|
||||
@ -133,16 +131,36 @@ function buildOrderTableColumns(): TableColumn[] {
|
||||
];
|
||||
}
|
||||
|
||||
function buildOrderTableFilters(): TableFilter[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
/*
|
||||
* Construct a table of build orders, according to the provided parameters
|
||||
*/
|
||||
export function BuildOrderTable({ params = {} }: { params?: any }) {
|
||||
const tableColumns = useMemo(() => buildOrderTableColumns(), []);
|
||||
const tableFilters = useMemo(() => buildOrderTableFilters(), []);
|
||||
|
||||
const tableFilters = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
// TODO: Filter by status code
|
||||
name: 'active',
|
||||
type: 'boolean',
|
||||
label: t`Active`
|
||||
},
|
||||
{
|
||||
name: 'overdue',
|
||||
type: 'boolean',
|
||||
label: t`Overdue`
|
||||
},
|
||||
{
|
||||
name: 'assigned_to_me',
|
||||
type: 'boolean',
|
||||
label: t`Assigned to me`
|
||||
}
|
||||
// TODO: 'assigned to' filter
|
||||
// TODO: 'issued by' filter
|
||||
// TODO: 'has project code' filter (see table_filters.js)
|
||||
// TODO: 'project code' filter (see table_filters.js)
|
||||
];
|
||||
}, []);
|
||||
|
||||
const navigate = useNavigate();
|
||||
|
||||
|
60
src/frontend/src/forms/BuildForms.tsx
Normal file
60
src/frontend/src/forms/BuildForms.tsx
Normal file
@ -0,0 +1,60 @@
|
||||
import {
|
||||
IconCalendar,
|
||||
IconLink,
|
||||
IconList,
|
||||
IconSitemap,
|
||||
IconTruckDelivery,
|
||||
IconUser,
|
||||
IconUsersGroup
|
||||
} from '@tabler/icons-react';
|
||||
|
||||
import { ApiFormFieldSet } from '../components/forms/fields/ApiFormField';
|
||||
|
||||
/**
|
||||
* Field set for BuildOrder forms
|
||||
*/
|
||||
export function buildOrderFields(): ApiFormFieldSet {
|
||||
return {
|
||||
reference: {},
|
||||
part: {
|
||||
filters: {
|
||||
assembly: true,
|
||||
virtual: false
|
||||
}
|
||||
},
|
||||
title: {},
|
||||
quantity: {},
|
||||
project_code: {
|
||||
icon: <IconList />
|
||||
},
|
||||
priority: {},
|
||||
parent: {
|
||||
icon: <IconSitemap />,
|
||||
filters: {
|
||||
part_detail: true
|
||||
}
|
||||
},
|
||||
sales_order: {
|
||||
icon: <IconTruckDelivery />
|
||||
},
|
||||
batch: {},
|
||||
target_date: {
|
||||
icon: <IconCalendar />
|
||||
},
|
||||
take_from: {},
|
||||
destination: {
|
||||
filters: {
|
||||
structural: false
|
||||
}
|
||||
},
|
||||
link: {
|
||||
icon: <IconLink />
|
||||
},
|
||||
issued_by: {
|
||||
icon: <IconUser />
|
||||
},
|
||||
responsible: {
|
||||
icon: <IconUsersGroup />
|
||||
}
|
||||
};
|
||||
}
|
@ -17,7 +17,7 @@ import {
|
||||
IconSitemap,
|
||||
IconTrash
|
||||
} from '@tabler/icons-react';
|
||||
import { useMemo } from 'react';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
import {
|
||||
@ -37,6 +37,8 @@ import { BuildOrderTable } from '../../components/tables/build/BuildOrderTable';
|
||||
import { AttachmentTable } from '../../components/tables/general/AttachmentTable';
|
||||
import { StockItemTable } from '../../components/tables/stock/StockItemTable';
|
||||
import { NotesEditor } from '../../components/widgets/MarkdownEditor';
|
||||
import { buildOrderFields } from '../../forms/BuildForms';
|
||||
import { openEditApiForm } from '../../functions/forms';
|
||||
import { useInstance } from '../../hooks/UseInstance';
|
||||
import { ApiPaths, apiUrl } from '../../states/ApiState';
|
||||
import { useUserState } from '../../states/UserState';
|
||||
@ -177,6 +179,25 @@ export default function BuildDetail() {
|
||||
];
|
||||
}, [build]);
|
||||
|
||||
const editBuildOrder = useCallback(() => {
|
||||
let fields = buildOrderFields();
|
||||
|
||||
// Cannot edit part field after creation
|
||||
fields['part'].hidden = true;
|
||||
|
||||
build.pk &&
|
||||
openEditApiForm({
|
||||
url: ApiPaths.build_order_list,
|
||||
pk: build.pk,
|
||||
title: t`Edit Build Order`,
|
||||
fields: fields,
|
||||
successMessage: t`Build Order updated`,
|
||||
onFormSuccess: () => {
|
||||
refreshInstance();
|
||||
}
|
||||
});
|
||||
}, [build]);
|
||||
|
||||
const buildActions = useMemo(() => {
|
||||
// TODO: Disable certain actions based on user permissions
|
||||
return [
|
||||
@ -211,7 +232,9 @@ export default function BuildDetail() {
|
||||
tooltip={t`Build Order Actions`}
|
||||
icon={<IconDots />}
|
||||
actions={[
|
||||
EditItemAction({}),
|
||||
EditItemAction({
|
||||
onClick: editBuildOrder
|
||||
}),
|
||||
DuplicateItemAction({}),
|
||||
DeleteItemAction({})
|
||||
]}
|
||||
|
@ -1,26 +1,42 @@
|
||||
import { t } from '@lingui/macro';
|
||||
import { Button, Stack, Text } from '@mantine/core';
|
||||
import { useCallback } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { PageDetail } from '../../components/nav/PageDetail';
|
||||
import { BuildOrderTable } from '../../components/tables/build/BuildOrderTable';
|
||||
import { notYetImplemented } from '../../functions/notifications';
|
||||
import { buildOrderFields } from '../../forms/BuildForms';
|
||||
import { openCreateApiForm } from '../../functions/forms';
|
||||
import { ApiPaths } from '../../states/ApiState';
|
||||
|
||||
/**
|
||||
* Build Order index page
|
||||
*/
|
||||
export default function BuildIndex() {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const newBuildOrder = useCallback(() => {
|
||||
openCreateApiForm({
|
||||
url: ApiPaths.build_order_list,
|
||||
title: t`Add Build Order`,
|
||||
fields: buildOrderFields(),
|
||||
successMessage: t`Build order created`,
|
||||
onFormSuccess: (data: any) => {
|
||||
if (data.pk) {
|
||||
navigate(`/build/${data.pk}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stack>
|
||||
<PageDetail
|
||||
title={t`Build Orders`}
|
||||
actions={[
|
||||
<Button
|
||||
key="new-build"
|
||||
color="green"
|
||||
onClick={() => notYetImplemented()}
|
||||
>
|
||||
<Text>{t`New Build Order`}</Text>
|
||||
<Button key="new-build" color="green" onClick={newBuildOrder}>
|
||||
{t`New Build Order`}
|
||||
</Button>
|
||||
]}
|
||||
/>
|
||||
|
@ -173,7 +173,7 @@ export default function CompanyDetail(props: CompanyDetailProps) {
|
||||
actions={[
|
||||
EditItemAction({
|
||||
disabled: !canEdit,
|
||||
callback: () => {
|
||||
onClick: () => {
|
||||
if (company?.pk) {
|
||||
editCompany({
|
||||
pk: company?.pk,
|
||||
|
@ -296,7 +296,7 @@ export default function PartDetail() {
|
||||
actions={[
|
||||
DuplicateItemAction({}),
|
||||
EditItemAction({
|
||||
callback: () => {
|
||||
onClick: () => {
|
||||
part.pk &&
|
||||
editPart({
|
||||
part_id: part.pk,
|
||||
|
@ -191,7 +191,7 @@ export default function StockDetail() {
|
||||
icon: <IconCopy />
|
||||
},
|
||||
EditItemAction({
|
||||
callback: () => {
|
||||
onClick: () => {
|
||||
stockitem.pk &&
|
||||
editStockItem({
|
||||
item_id: stockitem.pk,
|
||||
|
@ -100,9 +100,11 @@ export enum ApiPaths {
|
||||
company_list = 'api-company-list',
|
||||
company_attachment_list = 'api-company-attachment-list',
|
||||
supplier_part_list = 'api-supplier-part-list',
|
||||
manufacturer_part_list = 'api-manufacturer-part-list',
|
||||
|
||||
// Stock Item URLs
|
||||
stock_item_list = 'api-stock-item-list',
|
||||
stock_tracking_list = 'api-stock-tracking-list',
|
||||
stock_location_list = 'api-stock-location-list',
|
||||
stock_location_tree = 'api-stock-location-tree',
|
||||
stock_attachment_list = 'api-stock-attachment-list',
|
||||
@ -216,8 +218,12 @@ export function apiEndpoint(path: ApiPaths): string {
|
||||
return 'company/attachment/';
|
||||
case ApiPaths.supplier_part_list:
|
||||
return 'company/part/';
|
||||
case ApiPaths.manufacturer_part_list:
|
||||
return 'company/part/manufacturer/';
|
||||
case ApiPaths.stock_item_list:
|
||||
return 'stock/';
|
||||
case ApiPaths.stock_tracking_list:
|
||||
return 'stock/track/';
|
||||
case ApiPaths.stock_location_list:
|
||||
return 'stock/location/';
|
||||
case ApiPaths.stock_location_tree:
|
||||
|
Loading…
Reference in New Issue
Block a user