diff --git a/src/frontend/src/components/forms/ApiForm.tsx b/src/frontend/src/components/forms/ApiForm.tsx
index 3c7e526694..d9daf89b51 100644
--- a/src/frontend/src/components/forms/ApiForm.tsx
+++ b/src/frontend/src/components/forms/ApiForm.tsx
@@ -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
diff --git a/src/frontend/src/components/items/ActionDropdown.tsx b/src/frontend/src/components/items/ActionDropdown.tsx
index b5b9ce17c7..efbe6dee4b 100644
--- a/src/frontend/src/components/items/ActionDropdown.tsx
+++ b/src/frontend/src/components/items/ActionDropdown.tsx
@@ -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: ,
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: ,
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: ,
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: ,
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: ,
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: ,
name: t`Duplicate`,
tooltip: tooltip ?? t`Duplicate item`,
- onClick: callback,
+ onClick: onClick,
disabled: disabled
};
}
diff --git a/src/frontend/src/components/render/Generic.tsx b/src/frontend/src/components/render/Generic.tsx
new file mode 100644
index 0000000000..fb616aa692
--- /dev/null
+++ b/src/frontend/src/components/render/Generic.tsx
@@ -0,0 +1,14 @@
+import { ReactNode } from 'react';
+
+import { RenderInlineModel } from './Instance';
+
+export function RenderProjectCode({ instance }: { instance: any }): ReactNode {
+ return (
+ instance && (
+
+ )
+ );
+}
diff --git a/src/frontend/src/components/render/Instance.tsx b/src/frontend/src/components/render/Instance.tsx
index c87e61c394..2ace53f7ac 100644
--- a/src/frontend/src/components/render/Instance.tsx
+++ b/src/frontend/src/components/render/Instance.tsx
@@ -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,
diff --git a/src/frontend/src/components/render/ModelType.tsx b/src/frontend/src/components/render/ModelType.tsx
index 201ad8e39c..1976f95151 100644
--- a/src/frontend/src/components/render/ModelType.tsx
+++ b/src/frontend/src/components/render/ModelType.tsx
@@ -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`,
diff --git a/src/frontend/src/components/tables/build/BuildOrderTable.tsx b/src/frontend/src/components/tables/build/BuildOrderTable.tsx
index 2de0e78ad3..b39b2a85c3 100644
--- a/src/frontend/src/components/tables/build/BuildOrderTable.tsx
+++ b/src/frontend/src/components/tables/build/BuildOrderTable.tsx
@@ -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) => (
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();
diff --git a/src/frontend/src/forms/BuildForms.tsx b/src/frontend/src/forms/BuildForms.tsx
new file mode 100644
index 0000000000..af44bb70e6
--- /dev/null
+++ b/src/frontend/src/forms/BuildForms.tsx
@@ -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:
+ },
+ priority: {},
+ parent: {
+ icon: ,
+ filters: {
+ part_detail: true
+ }
+ },
+ sales_order: {
+ icon:
+ },
+ batch: {},
+ target_date: {
+ icon:
+ },
+ take_from: {},
+ destination: {
+ filters: {
+ structural: false
+ }
+ },
+ link: {
+ icon:
+ },
+ issued_by: {
+ icon:
+ },
+ responsible: {
+ icon:
+ }
+ };
+}
diff --git a/src/frontend/src/pages/build/BuildDetail.tsx b/src/frontend/src/pages/build/BuildDetail.tsx
index d8f6f21f72..42d01ffaf5 100644
--- a/src/frontend/src/pages/build/BuildDetail.tsx
+++ b/src/frontend/src/pages/build/BuildDetail.tsx
@@ -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={}
actions={[
- EditItemAction({}),
+ EditItemAction({
+ onClick: editBuildOrder
+ }),
DuplicateItemAction({}),
DeleteItemAction({})
]}
diff --git a/src/frontend/src/pages/build/BuildIndex.tsx b/src/frontend/src/pages/build/BuildIndex.tsx
index 7e6fafbd44..4e140bd980 100644
--- a/src/frontend/src/pages/build/BuildIndex.tsx
+++ b/src/frontend/src/pages/build/BuildIndex.tsx
@@ -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 (
<>
notYetImplemented()}
- >
- {t`New Build Order`}
+
]}
/>
diff --git a/src/frontend/src/pages/company/CompanyDetail.tsx b/src/frontend/src/pages/company/CompanyDetail.tsx
index 2c15fd536e..887681889c 100644
--- a/src/frontend/src/pages/company/CompanyDetail.tsx
+++ b/src/frontend/src/pages/company/CompanyDetail.tsx
@@ -173,7 +173,7 @@ export default function CompanyDetail(props: CompanyDetailProps) {
actions={[
EditItemAction({
disabled: !canEdit,
- callback: () => {
+ onClick: () => {
if (company?.pk) {
editCompany({
pk: company?.pk,
diff --git a/src/frontend/src/pages/part/PartDetail.tsx b/src/frontend/src/pages/part/PartDetail.tsx
index bc099996a5..2e26e6cc3d 100644
--- a/src/frontend/src/pages/part/PartDetail.tsx
+++ b/src/frontend/src/pages/part/PartDetail.tsx
@@ -296,7 +296,7 @@ export default function PartDetail() {
actions={[
DuplicateItemAction({}),
EditItemAction({
- callback: () => {
+ onClick: () => {
part.pk &&
editPart({
part_id: part.pk,
diff --git a/src/frontend/src/pages/stock/StockDetail.tsx b/src/frontend/src/pages/stock/StockDetail.tsx
index f7f0efd188..a13a3adeb6 100644
--- a/src/frontend/src/pages/stock/StockDetail.tsx
+++ b/src/frontend/src/pages/stock/StockDetail.tsx
@@ -191,7 +191,7 @@ export default function StockDetail() {
icon:
},
EditItemAction({
- callback: () => {
+ onClick: () => {
stockitem.pk &&
editStockItem({
item_id: stockitem.pk,
diff --git a/src/frontend/src/states/ApiState.tsx b/src/frontend/src/states/ApiState.tsx
index 2bbac38c8d..186b55dafe 100644
--- a/src/frontend/src/states/ApiState.tsx
+++ b/src/frontend/src/states/ApiState.tsx
@@ -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: