[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:
Oliver 2023-11-04 21:24:51 +11:00 committed by GitHub
parent 1640f605a0
commit fb7020a85a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 191 additions and 45 deletions

View File

@ -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

View File

@ -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
};
}

View 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}
/>
)
);
}

View File

@ -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,

View File

@ -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`,

View File

@ -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();

View 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 />
}
};
}

View File

@ -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({})
]}

View File

@ -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>
]}
/>

View File

@ -173,7 +173,7 @@ export default function CompanyDetail(props: CompanyDetailProps) {
actions={[
EditItemAction({
disabled: !canEdit,
callback: () => {
onClick: () => {
if (company?.pk) {
editCompany({
pk: company?.pk,

View File

@ -296,7 +296,7 @@ export default function PartDetail() {
actions={[
DuplicateItemAction({}),
EditItemAction({
callback: () => {
onClick: () => {
part.pk &&
editPart({
part_id: part.pk,

View File

@ -191,7 +191,7 @@ export default function StockDetail() {
icon: <IconCopy />
},
EditItemAction({
callback: () => {
onClick: () => {
stockitem.pk &&
editStockItem({
item_id: stockitem.pk,

View File

@ -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: