diff --git a/src/frontend/src/components/previews/PreviewType.tsx b/src/frontend/src/components/previews/PreviewType.tsx
index c3b0c3a433..5283f65389 100644
--- a/src/frontend/src/components/previews/PreviewType.tsx
+++ b/src/frontend/src/components/previews/PreviewType.tsx
@@ -3,11 +3,13 @@ import type { ReactNode } from 'react';
import { BuildOrderPreviewComponent } from './models/BuildOrderPreview';
import { CompanyPreviewComponent } from './models/CompanyPreview';
import { ManufacturerPartPreviewComponent } from './models/ManufacturerPartPreview';
+import { PartCategoryPreviewComponent } from './models/PartCategoryPreview';
import { PartPreviewComponent } from './models/PartPreview';
import { PurchaseOrderPreviewComponent } from './models/PurchaseOrderPreview';
import { ReturnOrderPreviewComponent } from './models/ReturnOrderPreview';
import { SalesOrderPreviewComponent } from './models/SalesOrderPreview';
import { SalesOrderShipmentPreviewComponent } from './models/SalesOrderShipmentPreview';
+import { StockLocationPreviewComponent } from './models/StockLocationPreview';
import { StockPreviewComponent } from './models/StockPreview';
import { SupplierPartPreviewComponent } from './models/SupplierPartPreview';
import { TransferOrderPreviewComponent } from './models/TransferOrderPreview';
@@ -55,6 +57,10 @@ export function getPreviewComponentForModel({
return SalesOrderShipmentPreviewComponent({ instance, modelId });
case ModelType.transferorder:
return TransferOrderPreviewComponent({ instance, modelId });
+ case ModelType.stocklocation:
+ return StockLocationPreviewComponent({ instance, modelId });
+ case ModelType.partcategory:
+ return PartCategoryPreviewComponent({ instance, modelId });
default:
return null;
}
diff --git a/src/frontend/src/components/previews/models/PartCategoryPreview.tsx b/src/frontend/src/components/previews/models/PartCategoryPreview.tsx
new file mode 100644
index 0000000000..537f6c97e4
--- /dev/null
+++ b/src/frontend/src/components/previews/models/PartCategoryPreview.tsx
@@ -0,0 +1,18 @@
+import { t } from '@lingui/core/macro';
+import { PartCategoryDetailsPanel } from '../../../pages/part/PartCategoryDetailsPanel';
+import type { PreviewType } from '../PreviewType';
+
+export function PartCategoryPreviewComponent({
+ instance,
+ modelId
+}: Readonly<{
+ instance: any;
+ modelId: number;
+}>): PreviewType {
+ const name = `${t`Part Category`} - ${instance?.name ?? `#${modelId}`}`;
+
+ return {
+ title: name,
+ preview:
+ };
+}
diff --git a/src/frontend/src/components/previews/models/StockLocationPreview.tsx b/src/frontend/src/components/previews/models/StockLocationPreview.tsx
new file mode 100644
index 0000000000..ffe0dc6536
--- /dev/null
+++ b/src/frontend/src/components/previews/models/StockLocationPreview.tsx
@@ -0,0 +1,18 @@
+import { t } from '@lingui/core/macro';
+import { StockLocationDetailsPanel } from '../../../pages/stock/StockLocationDetailsPanel';
+import type { PreviewType } from '../PreviewType';
+
+export function StockLocationPreviewComponent({
+ instance,
+ modelId
+}: Readonly<{
+ instance: any;
+ modelId: number;
+}>): PreviewType {
+ const name = `${t`Stock Location`} - ${instance?.name ?? `#${modelId}`}`;
+
+ return {
+ title: name,
+ preview:
+ };
+}
diff --git a/src/frontend/src/pages/part/CategoryDetail.tsx b/src/frontend/src/pages/part/CategoryDetail.tsx
index b457719353..57c73f19be 100644
--- a/src/frontend/src/pages/part/CategoryDetail.tsx
+++ b/src/frontend/src/pages/part/CategoryDetail.tsx
@@ -1,5 +1,5 @@
import { t } from '@lingui/core/macro';
-import { Group, LoadingOverlay, Skeleton, Stack } from '@mantine/core';
+import { LoadingOverlay, Stack } from '@mantine/core';
import {
IconCategory,
IconInfoCircle,
@@ -21,8 +21,6 @@ import type { PanelType } from '@lib/types/Panel';
import { useLocalStorage } from '@mantine/hooks';
import AdminButton from '../../components/buttons/AdminButton';
import StarredToggleButton from '../../components/buttons/StarredToggleButton';
-import type { DetailsField } from '../../components/details/Details';
-import { ItemDetailsGrid } from '../../components/details/ItemDetails';
import {
DeleteItemAction,
EditItemAction,
@@ -49,6 +47,7 @@ import { PartCategoryTable } from '../../tables/part/PartCategoryTable';
import PartCategoryTemplateTable from '../../tables/part/PartCategoryTemplateTable';
import { PartListTable } from '../../tables/part/PartTable';
import { StockItemTable } from '../../tables/stock/StockItemTable';
+import { PartCategoryDetailsPanel } from './PartCategoryDetailsPanel';
/**
* Detail view for a single PartCategory instance.
@@ -103,106 +102,6 @@ export default function CategoryDetail() {
assign: false
});
- const detailsPanel = useMemo(() => {
- if (id && instanceQuery.isFetching) {
- return ;
- }
-
- const left: DetailsField[] = [
- {
- type: 'text',
- name: 'name',
- label: t`Name`,
- copy: true,
- value_formatter: () => (
-
- {category.icon && }
- {category.name}
-
- )
- },
- {
- type: 'text',
- name: 'pathstring',
- label: t`Path`,
- icon: 'sitemap',
- copy: true,
- hidden: !id
- },
- {
- type: 'text',
- name: 'description',
- label: t`Description`,
- copy: true
- },
- {
- type: 'link',
- name: 'parent',
- model_field: 'name',
- icon: 'location',
- label: t`Parent Category`,
- model: ModelType.partcategory,
- hidden: !category?.parent
- },
- {
- type: 'boolean',
- name: 'starred',
- icon: 'notification',
- label: t`Subscribed`
- }
- ];
-
- const right: DetailsField[] = [
- {
- type: 'text',
- name: 'part_count',
- label: t`Parts`,
- icon: 'part',
- value_formatter: () => category?.part_count || '0'
- },
- {
- type: 'text',
- name: 'subcategories',
- label: t`Subcategories`,
- icon: 'sitemap',
- hidden: !category?.subcategories
- },
- {
- type: 'boolean',
- name: 'structural',
- label: t`Structural`,
- icon: 'sitemap'
- },
- {
- type: 'link',
- name: 'parent_default_location',
- label: t`Parent default location`,
- model: ModelType.stocklocation,
- hidden: !category.parent_default_location || category.default_location
- },
- {
- type: 'link',
- name: 'default_location',
- label: t`Default location`,
- model: ModelType.stocklocation,
- hidden: !category.default_location
- }
- ];
-
- return (
-
- );
- }, [category, instanceQuery]);
-
const editCategory = useEditApiFormModal({
url: ApiEndpoints.category_list,
pk: id,
@@ -299,7 +198,7 @@ export default function CategoryDetail() {
name: 'details',
label: t`Category Details`,
icon: ,
- content: detailsPanel,
+ content: ,
hidden: !id || !category?.pk
},
{
diff --git a/src/frontend/src/pages/part/PartCategoryDetailsPanel.tsx b/src/frontend/src/pages/part/PartCategoryDetailsPanel.tsx
new file mode 100644
index 0000000000..d8cb7f881a
--- /dev/null
+++ b/src/frontend/src/pages/part/PartCategoryDetailsPanel.tsx
@@ -0,0 +1,121 @@
+import { t } from '@lingui/core/macro';
+import { Group, Skeleton } from '@mantine/core';
+import { useMemo } from 'react';
+
+import { ModelType } from '@lib/enums/ModelType';
+
+import type { DetailsField } from '../../components/details/Details';
+import { ItemDetailsGrid } from '../../components/details/ItemDetails';
+import { useParameterDetailsGrid } from '../../components/details/ParameterDetailsGrid';
+import { ApiIcon } from '../../components/items/ApiIcon';
+
+export function PartCategoryDetailsPanel({
+ instance
+}: Readonly<{
+ instance: any;
+}>) {
+ const left: DetailsField[] = useMemo(
+ () => [
+ {
+ type: 'text',
+ name: 'name',
+ label: t`Name`,
+ copy: true,
+ value_formatter: () => (
+
+ {instance?.icon && }
+ {instance?.name}
+
+ )
+ },
+ {
+ type: 'text',
+ name: 'pathstring',
+ label: t`Path`,
+ icon: 'sitemap',
+ copy: true,
+ hidden: !instance?.pathstring
+ },
+ {
+ type: 'text',
+ name: 'description',
+ label: t`Description`,
+ copy: true
+ },
+ {
+ type: 'link',
+ name: 'parent',
+ model_field: 'name',
+ icon: 'location',
+ label: t`Parent Category`,
+ model: ModelType.partcategory,
+ hidden: !instance?.parent
+ },
+ {
+ type: 'boolean',
+ name: 'starred',
+ icon: 'notification',
+ label: t`Subscribed`
+ }
+ ],
+ [instance]
+ );
+
+ const right: DetailsField[] = useMemo(
+ () => [
+ {
+ type: 'text',
+ name: 'part_count',
+ label: t`Parts`,
+ icon: 'part',
+ value_formatter: () => instance?.part_count || '0'
+ },
+ {
+ type: 'text',
+ name: 'subcategories',
+ label: t`Subcategories`,
+ icon: 'sitemap',
+ hidden: !instance?.subcategories
+ },
+ {
+ type: 'boolean',
+ name: 'structural',
+ label: t`Structural`,
+ icon: 'sitemap'
+ },
+ {
+ type: 'link',
+ name: 'parent_default_location',
+ label: t`Parent default location`,
+ model: ModelType.stocklocation,
+ hidden:
+ !instance?.parent_default_location || !!instance?.default_location
+ },
+ {
+ type: 'link',
+ name: 'default_location',
+ label: t`Default location`,
+ model: ModelType.stocklocation,
+ hidden: !instance?.default_location
+ }
+ ],
+ [instance]
+ );
+
+ const parametersTable = useParameterDetailsGrid({
+ model_type: ModelType.partcategory,
+ model_id: instance?.pk
+ });
+
+ if (!instance?.pk) return ;
+
+ return (
+
+ );
+}
diff --git a/src/frontend/src/pages/stock/LocationDetail.tsx b/src/frontend/src/pages/stock/LocationDetail.tsx
index 7572e588d5..bcaf1c08b3 100644
--- a/src/frontend/src/pages/stock/LocationDetail.tsx
+++ b/src/frontend/src/pages/stock/LocationDetail.tsx
@@ -7,7 +7,7 @@ import type { TableFilter } from '@lib/index';
import type { StockOperationProps } from '@lib/types/Forms';
import type { PanelType } from '@lib/types/Panel';
import { t } from '@lingui/core/macro';
-import { Group, Skeleton, Stack } from '@mantine/core';
+import { Skeleton, Stack } from '@mantine/core';
import {
IconCalendar,
IconInfoCircle,
@@ -24,8 +24,6 @@ import { useBarcodeScanDialog } from '../../components/barcodes/BarcodeScanDialo
import AdminButton from '../../components/buttons/AdminButton';
import { PrintingActions } from '../../components/buttons/PrintingActions';
import OrderCalendar from '../../components/calendar/OrderCalendar';
-import type { DetailsField } from '../../components/details/Details';
-import { ItemDetailsGrid } from '../../components/details/ItemDetails';
import {
BarcodeActionDropdown,
DeleteItemAction,
@@ -57,6 +55,7 @@ import StockLocationParametricTable from '../../tables/stock/StockLocationParame
import { StockLocationTable } from '../../tables/stock/StockLocationTable';
import TransferOrderParametricTable from '../../tables/stock/TransferOrderParametricTable';
import { TransferOrderTable } from '../../tables/stock/TransferOrderTable';
+import { StockLocationDetailsPanel } from './StockLocationDetailsPanel';
function TransferOrderCalendar() {
const calendarFilters: TableFilter[] = useMemo(() => {
@@ -102,98 +101,12 @@ export default function Stock() {
}
});
- const detailsPanel = useMemo(() => {
- if (id && instanceQuery.isFetching) {
- return ;
- }
-
- const left: DetailsField[] = [
- {
- type: 'text',
- name: 'name',
- label: t`Name`,
- copy: true,
- value_formatter: () => (
-
- {location.icon && }
- {location.name}
-
- )
- },
- {
- type: 'text',
- name: 'pathstring',
- label: t`Path`,
- icon: 'sitemap',
- copy: true,
- hidden: !id
- },
- {
- type: 'text',
- name: 'description',
- label: t`Description`,
- copy: true
- },
- {
- type: 'link',
- name: 'parent',
- model_field: 'name',
- icon: 'location',
- label: t`Parent Location`,
- model: ModelType.stocklocation,
- hidden: !location?.parent
- }
- ];
-
- const right: DetailsField[] = [
- {
- type: 'text',
- name: 'items',
- icon: 'stock',
- label: t`Stock Items`,
- value_formatter: () => location?.items || '0'
- },
- {
- type: 'text',
- name: 'sublocations',
- icon: 'location',
- label: t`Sublocations`,
- hidden: !location?.sublocations
- },
- {
- type: 'boolean',
- name: 'structural',
- label: t`Structural`,
- icon: 'sitemap'
- },
- {
- 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'
- }
- ];
-
- return (
-
+ const detailsPanel =
+ id && instanceQuery.isFetching ? (
+
+ ) : (
+
);
- }, [location, instanceQuery]);
const [sublocationView, setSublocationView] = useState('table');
const [transferOrderView, setTransferOrderView] = useState('table');
diff --git a/src/frontend/src/pages/stock/StockLocationDetailsPanel.tsx b/src/frontend/src/pages/stock/StockLocationDetailsPanel.tsx
new file mode 100644
index 0000000000..22fb631548
--- /dev/null
+++ b/src/frontend/src/pages/stock/StockLocationDetailsPanel.tsx
@@ -0,0 +1,106 @@
+import { t } from '@lingui/core/macro';
+import { Group, Skeleton } from '@mantine/core';
+
+import { ModelType } from '@lib/enums/ModelType';
+
+import type { DetailsField } from '../../components/details/Details';
+import { ItemDetailsGrid } from '../../components/details/ItemDetails';
+import { useParameterDetailsGrid } from '../../components/details/ParameterDetailsGrid';
+import { ApiIcon } from '../../components/items/ApiIcon';
+
+export function StockLocationDetailsPanel({
+ instance
+}: Readonly<{
+ instance: any;
+}>) {
+ const left: DetailsField[] = [
+ {
+ type: 'text',
+ name: 'name',
+ label: t`Name`,
+ copy: true,
+ value_formatter: () => (
+
+ {instance?.icon && }
+ {instance?.name}
+
+ )
+ },
+ {
+ type: 'text',
+ name: 'pathstring',
+ label: t`Path`,
+ icon: 'sitemap',
+ copy: true,
+ hidden: !instance?.pathstring
+ },
+ {
+ type: 'text',
+ name: 'description',
+ label: t`Description`,
+ copy: true,
+ hidden: !instance?.description
+ },
+ {
+ type: 'link',
+ name: 'parent',
+ model_field: 'name',
+ icon: 'location',
+ label: t`Parent Location`,
+ model: ModelType.stocklocation,
+ hidden: !instance?.parent
+ }
+ ];
+
+ const right: DetailsField[] = [
+ {
+ type: 'text',
+ name: 'items',
+ icon: 'stock',
+ label: t`Stock Items`,
+ value_formatter: () => instance?.items || '0'
+ },
+ {
+ type: 'text',
+ name: 'sublocations',
+ icon: 'location',
+ label: t`Sublocations`,
+ hidden: !instance?.sublocations
+ },
+ {
+ type: 'boolean',
+ name: 'structural',
+ label: t`Structural`,
+ icon: 'sitemap'
+ },
+ {
+ type: 'boolean',
+ name: 'external',
+ label: t`External`
+ },
+ {
+ type: 'string',
+ name: 'location_type_detail.name',
+ label: t`Location Type`,
+ hidden: !instance?.location_type,
+ icon: 'packages'
+ }
+ ];
+
+ const parametersTable = useParameterDetailsGrid({
+ model_type: ModelType.stocklocation,
+ model_id: instance?.pk
+ });
+
+ if (!instance?.pk) return ;
+
+ return (
+
+ );
+}