diff --git a/src/frontend/src/components/details/Details.tsx b/src/frontend/src/components/details/Details.tsx
index 2161a3a4dd..374f35b556 100644
--- a/src/frontend/src/components/details/Details.tsx
+++ b/src/frontend/src/components/details/Details.tsx
@@ -12,6 +12,7 @@ import {
Tooltip
} from '@mantine/core';
import { useSuspenseQuery } from '@tanstack/react-query';
+import { getValueAtPath } from 'mantine-datatable';
import { Suspense, useCallback, useMemo } from 'react';
import { useNavigate } from 'react-router-dom';
@@ -43,7 +44,7 @@ export type PartIconsType = {
export type DetailsField =
| {
hidden?: boolean;
- icon?: string;
+ icon?: InvenTreeIconType;
name: string;
label?: string;
badge?: BadgeType;
@@ -382,6 +383,11 @@ export function DetailsTableField({
const FieldType: any = getFieldType(field.type);
+ const fieldValue = useMemo(
+ () => getValueAtPath(item, field.name) as string,
+ [item, field.name]
+ );
+
return (
-
+
{field.label}
-
+
- {field.copy && }
+ {field.copy && }
);
diff --git a/src/frontend/src/components/render/Instance.tsx b/src/frontend/src/components/render/Instance.tsx
index e3c4bbde97..fefb2ddb91 100644
--- a/src/frontend/src/components/render/Instance.tsx
+++ b/src/frontend/src/components/render/Instance.tsx
@@ -25,7 +25,11 @@ import {
RenderPartParameterTemplate,
RenderPartTestTemplate
} from './Part';
-import { RenderStockItem, RenderStockLocation } from './Stock';
+import {
+ RenderStockItem,
+ RenderStockLocation,
+ RenderStockLocationType
+} from './Stock';
import { RenderOwner, RenderUser } from './User';
type EnumDictionary = {
@@ -57,6 +61,7 @@ const RendererLookup: EnumDictionary<
[ModelType.salesorder]: RenderSalesOrder,
[ModelType.salesordershipment]: RenderSalesOrderShipment,
[ModelType.stocklocation]: RenderStockLocation,
+ [ModelType.stocklocationtype]: RenderStockLocationType,
[ModelType.stockitem]: RenderStockItem,
[ModelType.stockhistory]: RenderStockItem,
[ModelType.supplierpart]: RenderSupplierPart,
diff --git a/src/frontend/src/components/render/ModelType.tsx b/src/frontend/src/components/render/ModelType.tsx
index cc9c66fd73..421a1945a6 100644
--- a/src/frontend/src/components/render/ModelType.tsx
+++ b/src/frontend/src/components/render/ModelType.tsx
@@ -79,6 +79,11 @@ export const ModelInformationDict: ModelDict = {
cui_detail: '/stock/location/:pk/',
api_endpoint: ApiEndpoints.stock_location_list
},
+ stocklocationtype: {
+ label: t`Stock Location Type`,
+ label_multiple: t`Stock Location Types`,
+ api_endpoint: ApiEndpoints.stock_location_type_list
+ },
stockhistory: {
label: t`Stock History`,
label_multiple: t`Stock Histories`,
diff --git a/src/frontend/src/components/render/Stock.tsx b/src/frontend/src/components/render/Stock.tsx
index 1f8a60b995..4555cee4c6 100644
--- a/src/frontend/src/components/render/Stock.tsx
+++ b/src/frontend/src/components/render/Stock.tsx
@@ -17,6 +17,21 @@ export function RenderStockLocation({
);
}
+/**
+ * Inline rendering of a single StockLocationType instance
+ */
+export function RenderStockLocationType({
+ instance
+}: Readonly): ReactNode {
+ return (
+
+ );
+}
+
export function RenderStockItem({
instance
}: Readonly): ReactNode {
diff --git a/src/frontend/src/enums/ApiEndpoints.tsx b/src/frontend/src/enums/ApiEndpoints.tsx
index 43c9371052..8f703f1624 100644
--- a/src/frontend/src/enums/ApiEndpoints.tsx
+++ b/src/frontend/src/enums/ApiEndpoints.tsx
@@ -94,6 +94,7 @@ export enum ApiEndpoints {
stock_item_list = 'stock/',
stock_tracking_list = 'stock/track/',
stock_location_list = 'stock/location/',
+ stock_location_type_list = 'stock/location-type/',
stock_location_tree = 'stock/location/tree/',
stock_attachment_list = 'stock/attachment/',
stock_test_result_list = 'stock/test/',
diff --git a/src/frontend/src/enums/ModelType.tsx b/src/frontend/src/enums/ModelType.tsx
index e5d5155883..8f0574ef91 100644
--- a/src/frontend/src/enums/ModelType.tsx
+++ b/src/frontend/src/enums/ModelType.tsx
@@ -11,6 +11,7 @@ export enum ModelType {
projectcode = 'projectcode',
stockitem = 'stockitem',
stocklocation = 'stocklocation',
+ stocklocationtype = 'stocklocationtype',
stockhistory = 'stockhistory',
build = 'build',
buildline = 'buildline',
diff --git a/src/frontend/src/forms/StockForms.tsx b/src/frontend/src/forms/StockForms.tsx
index eb8e0bad27..fca1e2834c 100644
--- a/src/frontend/src/forms/StockForms.tsx
+++ b/src/frontend/src/forms/StockForms.tsx
@@ -819,6 +819,7 @@ export function stockLocationFields({}: {}): ApiFormFieldSet {
description: {},
structural: {},
external: {},
+ icon: {},
location_type: {}
};
diff --git a/src/frontend/src/pages/Index/Settings/AdminCenter/Index.tsx b/src/frontend/src/pages/Index/Settings/AdminCenter/Index.tsx
index 876651f886..cca88e567b 100644
--- a/src/frontend/src/pages/Index/Settings/AdminCenter/Index.tsx
+++ b/src/frontend/src/pages/Index/Settings/AdminCenter/Index.tsx
@@ -7,6 +7,7 @@ import {
IconExclamationCircle,
IconList,
IconListDetails,
+ IconPackages,
IconPlugConnected,
IconScale,
IconSitemap,
@@ -57,6 +58,10 @@ const PartCategoryTemplateTable = Loadable(
lazy(() => import('../../../../tables/part/PartCategoryTemplateTable'))
);
+const LocationTypesTable = Loadable(
+ lazy(() => import('../../../../tables/stock/LocationTypesTable'))
+);
+
const CurrencyTable = Loadable(
lazy(() => import('../../../../tables/settings/CurrencyTable'))
);
@@ -122,6 +127,12 @@ export default function AdminCenter() {
icon: ,
content:
},
+ {
+ name: 'location-types',
+ label: t`Location types`,
+ icon: ,
+ content:
+ },
{
name: 'templates',
label: t`Templates`,
diff --git a/src/frontend/src/pages/stock/LocationDetail.tsx b/src/frontend/src/pages/stock/LocationDetail.tsx
index 2f6e09cc44..2c8ce29b89 100644
--- a/src/frontend/src/pages/stock/LocationDetail.tsx
+++ b/src/frontend/src/pages/stock/LocationDetail.tsx
@@ -133,6 +133,14 @@ export default function Stock() {
type: 'boolean',
name: 'external',
label: t`External`
+ },
+ {
+ type: 'string',
+ // TODO: render location type icon here (ref: #7237)
+ name: 'location_type_detail.name',
+ label: t`Location Type`,
+ hidden: !location?.location_type,
+ icon: 'packages'
}
];
diff --git a/src/frontend/src/tables/part/ParametricPartTable.tsx b/src/frontend/src/tables/part/ParametricPartTable.tsx
index 378b572991..18bd3855ec 100644
--- a/src/frontend/src/tables/part/ParametricPartTable.tsx
+++ b/src/frontend/src/tables/part/ParametricPartTable.tsx
@@ -131,7 +131,7 @@ export default function ParametricPartTable({
const addParameter = useCreateApiFormModal({
url: ApiEndpoints.part_parameter_list,
title: t`Add Part Parameter`,
- fields: partParameterFields,
+ fields: useMemo(() => ({ ...partParameterFields }), [partParameterFields]),
focus: 'data',
onFormSuccess: (parameter: any) => {
updateParameterRecord(selectedPart, parameter);
@@ -146,7 +146,7 @@ export default function ParametricPartTable({
url: ApiEndpoints.part_parameter_list,
title: t`Edit Part Parameter`,
pk: selectedParameter,
- fields: partParameterFields,
+ fields: useMemo(() => ({ ...partParameterFields }), [partParameterFields]),
focus: 'data',
onFormSuccess: (parameter: any) => {
updateParameterRecord(selectedPart, parameter);
diff --git a/src/frontend/src/tables/part/PartCategoryTemplateTable.tsx b/src/frontend/src/tables/part/PartCategoryTemplateTable.tsx
index 6a262fd4d3..861747cb9b 100644
--- a/src/frontend/src/tables/part/PartCategoryTemplateTable.tsx
+++ b/src/frontend/src/tables/part/PartCategoryTemplateTable.tsx
@@ -36,7 +36,7 @@ export default function PartCategoryTemplateTable({}: {}) {
const newTemplate = useCreateApiFormModal({
url: ApiEndpoints.category_parameter_list,
title: t`Add Category Parameter`,
- fields: formFields,
+ fields: useMemo(() => ({ ...formFields }), [formFields]),
onFormSuccess: table.refreshTable
});
@@ -44,7 +44,7 @@ export default function PartCategoryTemplateTable({}: {}) {
url: ApiEndpoints.category_parameter_list,
pk: selectedTemplate,
title: t`Edit Category Parameter`,
- fields: formFields,
+ fields: useMemo(() => ({ ...formFields }), [formFields]),
onFormSuccess: (record: any) => table.updateRecord(record)
});
diff --git a/src/frontend/src/tables/part/PartParameterTable.tsx b/src/frontend/src/tables/part/PartParameterTable.tsx
index 65c3740812..f6550d642d 100644
--- a/src/frontend/src/tables/part/PartParameterTable.tsx
+++ b/src/frontend/src/tables/part/PartParameterTable.tsx
@@ -110,7 +110,7 @@ export function PartParameterTable({ partId }: { partId: any }) {
const newParameter = useCreateApiFormModal({
url: ApiEndpoints.part_parameter_list,
title: t`New Part Parameter`,
- fields: partParameterFields,
+ fields: useMemo(() => ({ ...partParameterFields }), [partParameterFields]),
focus: 'template',
initialData: {
part: partId
@@ -126,7 +126,7 @@ export function PartParameterTable({ partId }: { partId: any }) {
url: ApiEndpoints.part_parameter_list,
pk: selectedParameter,
title: t`Edit Part Parameter`,
- fields: partParameterFields,
+ fields: useMemo(() => ({ ...partParameterFields }), [partParameterFields]),
onFormSuccess: table.refreshTable
});
diff --git a/src/frontend/src/tables/part/PartParameterTemplateTable.tsx b/src/frontend/src/tables/part/PartParameterTemplateTable.tsx
index 0442eb36de..5b2256c3ca 100644
--- a/src/frontend/src/tables/part/PartParameterTemplateTable.tsx
+++ b/src/frontend/src/tables/part/PartParameterTemplateTable.tsx
@@ -83,7 +83,10 @@ export default function PartParameterTemplateTable() {
const newTemplate = useCreateApiFormModal({
url: ApiEndpoints.part_parameter_template_list,
title: t`Add Parameter Template`,
- fields: partParameterTemplateFields,
+ fields: useMemo(
+ () => ({ ...partParameterTemplateFields }),
+ [partParameterTemplateFields]
+ ),
onFormSuccess: table.refreshTable
});
@@ -95,7 +98,10 @@ export default function PartParameterTemplateTable() {
url: ApiEndpoints.part_parameter_template_list,
pk: selectedTemplate,
title: t`Edit Parameter Template`,
- fields: partParameterTemplateFields,
+ fields: useMemo(
+ () => ({ ...partParameterTemplateFields }),
+ [partParameterTemplateFields]
+ ),
onFormSuccess: (record: any) => table.updateRecord(record)
});
diff --git a/src/frontend/src/tables/part/PartTestTemplateTable.tsx b/src/frontend/src/tables/part/PartTestTemplateTable.tsx
index f96fda50b0..b92140e0f3 100644
--- a/src/frontend/src/tables/part/PartTestTemplateTable.tsx
+++ b/src/frontend/src/tables/part/PartTestTemplateTable.tsx
@@ -124,7 +124,10 @@ export default function PartTestTemplateTable({ partId }: { partId: number }) {
const newTestTemplate = useCreateApiFormModal({
url: ApiEndpoints.part_test_template_list,
title: t`Add Test Template`,
- fields: partTestTemplateFields,
+ fields: useMemo(
+ () => ({ ...partTestTemplateFields }),
+ [partTestTemplateFields]
+ ),
initialData: {
part: partId
},
@@ -137,7 +140,10 @@ export default function PartTestTemplateTable({ partId }: { partId: number }) {
url: ApiEndpoints.part_test_template_list,
pk: selectedTest,
title: t`Edit Test Template`,
- fields: partTestTemplateFields,
+ fields: useMemo(
+ () => ({ ...partTestTemplateFields }),
+ [partTestTemplateFields]
+ ),
onFormSuccess: (record: any) => table.updateRecord(record)
});
diff --git a/src/frontend/src/tables/stock/LocationTypesTable.tsx b/src/frontend/src/tables/stock/LocationTypesTable.tsx
new file mode 100644
index 0000000000..c2b5afcae5
--- /dev/null
+++ b/src/frontend/src/tables/stock/LocationTypesTable.tsx
@@ -0,0 +1,128 @@
+import { t } from '@lingui/macro';
+import { useCallback, useMemo, useState } from 'react';
+
+import { AddItemButton } from '../../components/buttons/AddItemButton';
+import { ApiFormFieldSet } from '../../components/forms/fields/ApiFormField';
+import { ApiEndpoints } from '../../enums/ApiEndpoints';
+import { UserRoles } from '../../enums/Roles';
+import {
+ useCreateApiFormModal,
+ useDeleteApiFormModal,
+ useEditApiFormModal
+} from '../../hooks/UseForm';
+import { useTable } from '../../hooks/UseTable';
+import { apiUrl } from '../../states/ApiState';
+import { useUserState } from '../../states/UserState';
+import { TableColumn } from '../Column';
+import { InvenTreeTable } from '../InvenTreeTable';
+import { RowDeleteAction, RowEditAction } from '../RowActions';
+
+export default function LocationTypesTable() {
+ const table = useTable('location-types');
+ const user = useUserState();
+
+ const formFields: ApiFormFieldSet = useMemo(() => {
+ return {
+ name: {},
+ description: {},
+ icon: {}
+ };
+ }, []);
+
+ const [selectedLocationType, setSelectedLocationType] = useState(0);
+
+ const newLocationType = useCreateApiFormModal({
+ url: ApiEndpoints.stock_location_type_list,
+ title: t`Add Location Type`,
+ fields: useMemo(() => ({ ...formFields }), [formFields]),
+ onFormSuccess: table.refreshTable
+ });
+
+ const editLocationType = useEditApiFormModal({
+ url: ApiEndpoints.stock_location_type_list,
+ pk: selectedLocationType,
+ title: t`Edit Location Type`,
+ fields: useMemo(() => ({ ...formFields }), [formFields]),
+ onFormSuccess: (record: any) => table.updateRecord(record)
+ });
+
+ const deleteLocationType = useDeleteApiFormModal({
+ url: ApiEndpoints.stock_location_type_list,
+ pk: selectedLocationType,
+ title: t`Delete Location Type`,
+ onFormSuccess: table.refreshTable
+ });
+
+ const tableColumns: TableColumn[] = useMemo(() => {
+ return [
+ {
+ accessor: 'name',
+ title: t`Name`,
+ sortable: true
+ },
+ {
+ accessor: 'description',
+ title: t`Description`
+ },
+ {
+ accessor: 'icon',
+ title: t`Icon`,
+ sortable: true
+ },
+ {
+ accessor: 'location_count',
+ sortable: true
+ }
+ ];
+ }, []);
+
+ const rowActions = useCallback(
+ (record: any) => {
+ return [
+ RowEditAction({
+ hidden: !user.hasChangeRole(UserRoles.stock_location),
+ onClick: () => {
+ setSelectedLocationType(record.pk);
+ editLocationType.open();
+ }
+ }),
+ RowDeleteAction({
+ hidden: !user.hasDeleteRole(UserRoles.stock_location),
+ onClick: () => {
+ setSelectedLocationType(record.pk);
+ deleteLocationType.open();
+ }
+ })
+ ];
+ },
+ [user]
+ );
+
+ const tableActions = useMemo(() => {
+ return [
+ newLocationType.open()}
+ hidden={!user.hasAddRole(UserRoles.stock_location)}
+ />
+ ];
+ }, [user]);
+
+ return (
+ <>
+ {newLocationType.modal}
+ {editLocationType.modal}
+ {deleteLocationType.modal}
+
+ >
+ );
+}