diff --git a/src/frontend/src/components/forms/fields/ChoiceField.tsx b/src/frontend/src/components/forms/fields/ChoiceField.tsx
index a65f630463..5b7757645d 100644
--- a/src/frontend/src/components/forms/fields/ChoiceField.tsx
+++ b/src/frontend/src/components/forms/fields/ChoiceField.tsx
@@ -32,7 +32,7 @@ export function ChoiceField({
return choices.map((choice) => {
return {
value: choice.value.toString(),
- label: choice.display_name.toString()
+ label: choice.display_name ?? choice.value
};
});
}, [definition.choices]);
diff --git a/src/frontend/src/components/forms/fields/TableField.tsx b/src/frontend/src/components/forms/fields/TableField.tsx
index baf4aa5968..302c2f0210 100644
--- a/src/frontend/src/components/forms/fields/TableField.tsx
+++ b/src/frontend/src/components/forms/fields/TableField.tsx
@@ -1,5 +1,5 @@
import { Trans, t } from '@lingui/macro';
-import { Table } from '@mantine/core';
+import { Container, Flex, Group, Table } from '@mantine/core';
import { FieldValues, UseControllerReturn } from 'react-hook-form';
import { InvenTreeIcon } from '../../../functions/icons';
@@ -34,19 +34,21 @@ export function TableField({
return (
-
-
+
+
{definition.headers?.map((header) => {
return {header} | ;
})}
-
-
-
+
+
+
{value.length > 0 ? (
value.map((item: any, idx: number) => {
// Table fields require render function
if (!definition.modelRenderer) {
- return {t`modelRenderer entry required for tables`}
;
+ return (
+ {t`modelRenderer entry required for tables`}
+ );
}
return definition.modelRenderer({
item: item,
@@ -56,8 +58,8 @@ export function TableField({
});
})
) : (
-
-
+
@@ -71,10 +73,36 @@ export function TableField({
No entries available
- |
-
+
+
)}
-
+
);
}
+
+/*
+ * Display an "extra" row below the main table row, for additional information.
+ */
+export function TableFieldExtraRow({
+ visible,
+ content,
+ colSpan
+}: {
+ visible: boolean;
+ content: React.ReactNode;
+ colSpan?: number;
+}) {
+ return (
+ visible && (
+
+
+
+
+ {content}
+
+
+
+ )
+ );
+}
diff --git a/src/frontend/src/components/items/ActionDropdown.tsx b/src/frontend/src/components/items/ActionDropdown.tsx
index dd91918737..59045bb928 100644
--- a/src/frontend/src/components/items/ActionDropdown.tsx
+++ b/src/frontend/src/components/items/ActionDropdown.tsx
@@ -189,10 +189,12 @@ export function EditItemAction({
// Common action button for deleting an item
export function DeleteItemAction({
hidden = false,
+ disabled = false,
tooltip,
onClick
}: {
hidden?: boolean;
+ disabled?: boolean;
tooltip?: string;
onClick?: () => void;
}): ActionDropdownItem {
@@ -201,7 +203,8 @@ export function DeleteItemAction({
name: t`Delete`,
tooltip: tooltip ?? t`Delete item`,
onClick: onClick,
- hidden: hidden
+ hidden: hidden,
+ disabled: disabled
};
}
diff --git a/src/frontend/src/components/modals/ServerInfoModal.tsx b/src/frontend/src/components/modals/ServerInfoModal.tsx
index f74e4a6ec5..7fad8a6fda 100644
--- a/src/frontend/src/components/modals/ServerInfoModal.tsx
+++ b/src/frontend/src/components/modals/ServerInfoModal.tsx
@@ -26,46 +26,46 @@ export function ServerInfoModal({
Server
-
-
-
+
+
+
Instance Name
- |
- {server.instance} |
-
-
-
+
+ {server.instance}
+
+
+
Database
- |
-
+
+
{server.database}
- |
-
+
+
{server.debug_mode && (
-
-
+
+
Debug Mode
- |
-
+
+
Server is running in debug mode
- |
-
+
+
)}
{server.docker_mode && (
-
-
+
+
Docker Mode
- |
-
+
+
Server is deployed using docker
- |
-
+
+
)}
-
-
+
+
Plugin Support
- |
-
+
+
{server.plugins_enabled ? (
Plugin support enabled
@@ -73,13 +73,13 @@ export function ServerInfoModal({
Plugin support disabled
)}
- |
-
-
-
+
+
+
+
Server status
- |
-
+
+
{server.system_health ? (
@@ -89,52 +89,52 @@ export function ServerInfoModal({
)}
- |
-
+
+
{server.worker_running != true && (
-
-
+
+
Background Worker
- |
-
+
+
Background worker not running
- |
-
+
+
)}
{server.email_configured != true && (
-
-
+
+
Email Settings
- |
-
+
+
Email settings not configured
- |
-
+
+
)}
-
+
Version
-
-
-
+
+
+
Server Version
- |
- {server.version} |
-
-
-
+
+ {server.version}
+
+
+
API Version
- |
- {server.apiVersion} |
-
-
+
+ {server.apiVersion}
+
+
diff --git a/src/frontend/src/forms/PurchaseOrderForms.tsx b/src/frontend/src/forms/PurchaseOrderForms.tsx
index 930e729571..a5ad45d826 100644
--- a/src/frontend/src/forms/PurchaseOrderForms.tsx
+++ b/src/frontend/src/forms/PurchaseOrderForms.tsx
@@ -1,5 +1,12 @@
import { t } from '@lingui/macro';
-import { Flex, FocusTrap, Modal, NumberInput, TextInput } from '@mantine/core';
+import {
+ Flex,
+ FocusTrap,
+ Modal,
+ NumberInput,
+ Table,
+ TextInput
+} from '@mantine/core';
import { useDisclosure } from '@mantine/hooks';
import {
IconAddressBook,
@@ -24,6 +31,7 @@ import {
ApiFormAdjustFilterType,
ApiFormFieldSet
} from '../components/forms/fields/ApiFormField';
+import { TableFieldExtraRow } from '../components/forms/fields/TableField';
import { Thumbnail } from '../components/images/Thumbnail';
import { ProgressBar } from '../components/items/ProgressBar';
import { StylishText } from '../components/items/StylishText';
@@ -308,8 +316,8 @@ function LineItemFormRow({
/>
-
-
+
+
{record.part_detail.name}
- |
- {record.supplier_part_detail.SKU} |
-
+
+ {record.supplier_part_detail.SKU}
+
- |
-
+
+
input.changeFn(input.idx, 'quantity', value)}
/>
- |
-
+
+
locationHandlers.toggle()}
@@ -387,11 +395,11 @@ function LineItemFormRow({
color="red"
/>
- |
-
+
+
{locationOpen && (
-
-
+
+
- |
-
+
+
- |
-
- )}
- {batchOpen && (
- <>
-
-
-
-
- setBatchCode(value),
- label: 'Batch Code',
- value: batchCode
- }}
- />
-
-
- |
-
-
-
-
-
- |
-
- {record.trackable && (
-
-
-
-
- setSerials(value),
- label: 'Serial numbers',
- value: serials
- }}
- />
-
-
- |
-
-
-
-
-
- |
-
- )}
- >
- )}
- {statusOpen && (
-
-
-
- input.changeFn(input.idx, 'status', value)
- }}
- defaultValue={10}
- />
- |
-
-
-
-
-
-
- |
-
+
+
)}
+ setBatchCode(value),
+ label: 'Batch Code',
+ value: batchCode
+ }}
+ />
+ }
+ />
+ setSerials(value),
+ label: 'Serial numbers',
+ value: serials
+ }}
+ />
+ }
+ />
+
+ input.changeFn(input.idx, 'status', value)
+ }}
+ defaultValue={10}
+ />
+ }
+ />
>
);
}
@@ -655,11 +610,11 @@ export function useReceiveLineItems(props: LineItemsForm) {
return useCreateApiFormModal({
...props.formProps,
url: url,
- title: t`Receive line items`,
+ title: t`Receive Line Items`,
fields: fields,
initialData: {
location: null
},
- size: 'max(60%,800px)'
+ size: 'xl'
});
}
diff --git a/src/frontend/src/forms/StockForms.tsx b/src/frontend/src/forms/StockForms.tsx
index d7dfedc0e8..eb8e0bad27 100644
--- a/src/frontend/src/forms/StockForms.tsx
+++ b/src/frontend/src/forms/StockForms.tsx
@@ -1,5 +1,5 @@
import { t } from '@lingui/macro';
-import { Flex, Group, NumberInput, Skeleton, Text } from '@mantine/core';
+import { Flex, Group, NumberInput, Skeleton, Table, Text } from '@mantine/core';
import { modals } from '@mantine/modals';
import { useQuery, useSuspenseQuery } from '@tanstack/react-query';
import { Suspense, useCallback, useMemo, useState } from 'react';
@@ -297,8 +297,8 @@ function StockOperationsRow({
return !record ? (
{t`Loading...`}
) : (
-
-
+
+
{record.part_detail?.name}
- |
- {record.location ? record.location_detail?.pathstring : '-'} |
-
+
+
+ {record.location ? record.location_detail?.pathstring : '-'}
+
+
{stockString}
- |
+
{!merge && (
-
+
- |
+
)}
-
+
{transfer && (
- |
-
+
+
);
}
diff --git a/src/frontend/src/functions/icons.tsx b/src/frontend/src/functions/icons.tsx
index 6484003eb1..94ab038dd0 100644
--- a/src/frontend/src/functions/icons.tsx
+++ b/src/frontend/src/functions/icons.tsx
@@ -22,6 +22,7 @@ import {
IconClipboardText,
IconCopy,
IconCornerDownLeft,
+ IconCornerDownRight,
IconCornerUpRightDouble,
IconCurrencyDollar,
IconDots,
@@ -190,6 +191,7 @@ const icons = {
phone: IconPhone,
sitemap: IconSitemap,
downleft: IconCornerDownLeft,
+ downright: IconCornerDownRight,
barcode: IconQrcode,
barLine: IconMinusVertical,
batch_code: IconClipboardText,
diff --git a/src/frontend/src/pages/Index/Settings/AccountSettings/UserThemePanel.tsx b/src/frontend/src/pages/Index/Settings/AccountSettings/UserThemePanel.tsx
index 7765e888a3..fa3bb9cf70 100644
--- a/src/frontend/src/pages/Index/Settings/AccountSettings/UserThemePanel.tsx
+++ b/src/frontend/src/pages/Index/Settings/AccountSettings/UserThemePanel.tsx
@@ -81,41 +81,41 @@ export function UserTheme({ height }: { height: number }) {
Theme
-
-
-
+
+
+
Primary color
- |
-
+
+
- |
-
-
-
+
+
+
+
White color
- |
-
+
+
- |
-
-
-
+
+
+
+
Black color
- |
-
+
+
- |
-
-
-
+
+
+
+
Border Radius
- |
-
+
+
getMark(val).label}
defaultValue={50}
@@ -125,13 +125,13 @@ export function UserTheme({ height }: { height: number }) {
onChange={changeRadius}
mb={18}
/>
- |
-
-
-
+
+
+
+
Loader
- |
-
+
+
- |
-
-
+
+
+
);
diff --git a/src/frontend/src/pages/part/CategoryDetail.tsx b/src/frontend/src/pages/part/CategoryDetail.tsx
index 9db7350807..d82ad4c29a 100644
--- a/src/frontend/src/pages/part/CategoryDetail.tsx
+++ b/src/frontend/src/pages/part/CategoryDetail.tsx
@@ -8,12 +8,13 @@ import {
IconSitemap
} from '@tabler/icons-react';
import { useMemo, useState } from 'react';
-import { useParams } from 'react-router-dom';
+import { useNavigate, useParams } from 'react-router-dom';
import { DetailsField, DetailsTable } from '../../components/details/Details';
import { ItemDetailsGrid } from '../../components/details/ItemDetails';
import {
ActionDropdown,
+ DeleteItemAction,
EditItemAction
} from '../../components/items/ActionDropdown';
import { PageDetail } from '../../components/nav/PageDetail';
@@ -24,7 +25,10 @@ import { ModelType } from '../../enums/ModelType';
import { UserRoles } from '../../enums/Roles';
import { partCategoryFields } from '../../forms/PartForms';
import { getDetailUrl } from '../../functions/urls';
-import { useEditApiFormModal } from '../../hooks/UseForm';
+import {
+ useDeleteApiFormModal,
+ useEditApiFormModal
+} from '../../hooks/UseForm';
import { useInstance } from '../../hooks/UseInstance';
import { useUserState } from '../../states/UserState';
import ParametricPartTable from '../../tables/part/ParametricPartTable';
@@ -43,6 +47,7 @@ export default function CategoryDetail({}: {}) {
[_id]
);
+ const navigate = useNavigate();
const user = useUserState();
const [treeOpen, setTreeOpen] = useState(false);
@@ -154,6 +159,46 @@ export default function CategoryDetail({}: {}) {
onFormSuccess: refreshInstance
});
+ const deleteOptions = useMemo(() => {
+ return [
+ {
+ value: 0,
+ display_name: `Move items to parent category`
+ },
+ {
+ value: 1,
+ display_name: t`Delete items`
+ }
+ ];
+ }, []);
+
+ const deleteCategory = useDeleteApiFormModal({
+ url: ApiEndpoints.category_list,
+ pk: id,
+ title: t`Delete Part Category`,
+ fields: {
+ delete_parts: {
+ label: t`Parts Action`,
+ description: t`Action for parts in this category`,
+ choices: deleteOptions,
+ field_type: 'choice'
+ },
+ delete_child_categories: {
+ label: t`Child Categories Action`,
+ description: t`Action for child categories in this category`,
+ choices: deleteOptions,
+ field_type: 'choice'
+ }
+ },
+ onFormSuccess: () => {
+ if (category.parent) {
+ navigate(getDetailUrl(ModelType.partcategory, category.parent));
+ } else {
+ navigate('/part/');
+ }
+ }
+ });
+
const categoryActions = useMemo(() => {
return [
editCategory.open()
+ }),
+ DeleteItemAction({
+ hidden: !id || !user.hasDeleteRole(UserRoles.part_category),
+ tooltip: t`Delete Part Category`,
+ onClick: () => deleteCategory.open()
})
]}
/>
@@ -223,6 +273,7 @@ export default function CategoryDetail({}: {}) {
return (
<>
{editCategory.modal}
+ {deleteCategory.modal}
-
@@ -700,6 +710,26 @@ export default function PartDetail() {
modelType: ModelType.part
});
+ const deletePart = useDeleteApiFormModal({
+ url: ApiEndpoints.part_list,
+ pk: part.pk,
+ title: t`Delete Part`,
+ onFormSuccess: () => {
+ if (part.category) {
+ navigate(getDetailUrl(ModelType.partcategory, part.category));
+ } else {
+ navigate('/part/');
+ }
+ },
+ preFormContent: (
+
+
+
+
+
+ )
+ });
+
const stockActionProps: StockOperationProps = useMemo(() => {
return {
pk: part.pk,
@@ -771,7 +801,9 @@ export default function PartDetail() {
onClick: () => editPart.open()
}),
DeleteItemAction({
- hidden: part?.active || !user.hasDeleteRole(UserRoles.part)
+ hidden: !user.hasDeleteRole(UserRoles.part),
+ disabled: part.active,
+ onClick: () => deletePart.open()
})
]}
/>
@@ -782,6 +814,7 @@ export default function PartDetail() {
<>
{duplicatePart.modal}
{editPart.modal}
+ {deletePart.modal}
{
+ return [
+ {
+ value: 0,
+ display_name: `Move items to parent location`
+ },
+ {
+ value: 1,
+ display_name: t`Delete items`
+ }
+ ];
+ }, []);
+
+ const deleteLocation = useDeleteApiFormModal({
+ url: ApiEndpoints.stock_location_list,
+ pk: id,
+ title: t`Delete Stock Location`,
+ fields: {
+ delete_stock_items: {
+ label: t`Items Action`,
+ description: t`Action for stock items in this location`,
+ field_type: 'choice',
+ choices: deleteOptions
+ },
+ delete_sub_location: {
+ label: t`Child Locations Action`,
+ description: t`Action for child locations in this location`,
+ field_type: 'choice',
+ choices: deleteOptions
+ }
+ },
+ onFormSuccess: () => {
+ if (location.parent) {
+ navigate(getDetailUrl(ModelType.stocklocation, location.parent));
+ } else {
+ navigate('/stock/');
+ }
+ }
+ });
+
const stockItemActionProps: StockOperationProps = useMemo(() => {
return {
pk: location.pk,
@@ -282,6 +327,11 @@ export default function Stock() {
hidden: !id || !user.hasChangeRole(UserRoles.stock_location),
tooltip: t`Edit Stock Location`,
onClick: () => editLocation.open()
+ }),
+ DeleteItemAction({
+ hidden: !id || !user.hasDeleteRole(UserRoles.stock_location),
+ tooltip: t`Delete Stock Location`,
+ onClick: () => deleteLocation.open()
})
]}
/>
@@ -303,6 +353,7 @@ export default function Stock() {
return (
<>
{editLocation.modal}
+ {deleteLocation.modal}
{
+ // TODO: Fill this out with information on the stock item.
+ // e.g. list of child items which would be deleted, etc
+ return undefined;
+ }, [stockitem]);
+
+ const deleteStockItem = useDeleteApiFormModal({
+ url: ApiEndpoints.stock_item_list,
+ pk: stockitem.pk,
+ title: t`Delete Stock Item`,
+ preFormContent: preDeleteContent,
+ onFormSuccess: () => {
+ // Redirect to the part page
+ navigate(getDetailUrl(ModelType.part, stockitem.part));
+ }
+ });
+
const stockActionProps: StockOperationProps = useMemo(() => {
return {
items: stockitem,
@@ -458,7 +478,8 @@ export default function StockDetail() {
onClick: () => editStockItem.open()
}),
DeleteItemAction({
- hidden: !user.hasDeleteRole(UserRoles.stock)
+ hidden: !user.hasDeleteRole(UserRoles.stock),
+ onClick: () => deleteStockItem.open()
})
]}
/>
@@ -524,6 +545,7 @@ export default function StockDetail() {
{editStockItem.modal}
{duplicateStockItem.modal}
+ {deleteStockItem.modal}
{countStockItem.modal}
{addStockItem.modal}
{removeStockItem.modal}