diff --git a/src/frontend/src/components/forms/fields/ApiFormField.tsx b/src/frontend/src/components/forms/fields/ApiFormField.tsx
index 19a9655183..de772fbeff 100644
--- a/src/frontend/src/components/forms/fields/ApiFormField.tsx
+++ b/src/frontend/src/components/forms/fields/ApiFormField.tsx
@@ -14,6 +14,7 @@ import { IconX } from '@tabler/icons-react';
import { ReactNode } from 'react';
import { useMemo } from 'react';
+import { ModelType } from '../../render/ModelType';
import { ApiFormProps } from '../ApiForm';
import { ChoiceField } from './ChoiceField';
import { RelatedModelField } from './RelatedModelField';
@@ -63,7 +64,7 @@ export type ApiFormFieldType = {
fieldType?: string;
api_url?: string;
read_only?: boolean;
- model?: string;
+ model?: ModelType;
filters?: any;
required?: boolean;
choices?: any[];
diff --git a/src/frontend/src/components/forms/fields/RelatedModelField.tsx b/src/frontend/src/components/forms/fields/RelatedModelField.tsx
index 5341d43b09..c13cac5d9b 100644
--- a/src/frontend/src/components/forms/fields/RelatedModelField.tsx
+++ b/src/frontend/src/components/forms/fields/RelatedModelField.tsx
@@ -160,7 +160,7 @@ export function RelatedModelField({
// TODO: If a custom render function is provided, use that
return (
-
+
);
}
diff --git a/src/frontend/src/components/nav/SearchDrawer.tsx b/src/frontend/src/components/nav/SearchDrawer.tsx
index 19f34a6120..a8bf7de3a3 100644
--- a/src/frontend/src/components/nav/SearchDrawer.tsx
+++ b/src/frontend/src/components/nav/SearchDrawer.tsx
@@ -31,11 +31,11 @@ import { useNavigate } from 'react-router-dom';
import { api } from '../../App';
import { RenderInstance } from '../render/Instance';
+import { ModelInformationDict, ModelType } from '../render/ModelType';
// Define type for handling individual search queries
type SearchQuery = {
- name: string;
- title: string;
+ name: ModelType;
enabled: boolean;
parameters: any;
results?: any;
@@ -57,16 +57,14 @@ function settingsCheck(setting: string) {
function buildSearchQueries(): SearchQuery[] {
return [
{
- name: 'part',
- title: t`Parts`,
+ name: ModelType.part,
parameters: {},
enabled:
permissionCheck('part.view') &&
settingsCheck('SEARCH_PREVIEW_SHOW_PARTS')
},
{
- name: 'supplierpart',
- title: t`Supplier Parts`,
+ name: ModelType.supplierpart,
parameters: {
part_detail: true,
supplier_detail: true,
@@ -78,8 +76,7 @@ function buildSearchQueries(): SearchQuery[] {
settingsCheck('SEARCH_PREVIEW_SHOW_SUPPLIER_PARTS')
},
{
- name: 'manufacturerpart',
- title: t`Manufacturer Parts`,
+ name: ModelType.manufacturerpart,
parameters: {
part_detail: true,
supplier_detail: true,
@@ -91,16 +88,14 @@ function buildSearchQueries(): SearchQuery[] {
settingsCheck('SEARCH_PREVIEW_SHOW_MANUFACTURER_PARTS')
},
{
- name: 'partcategory',
- title: t`Part Categories`,
+ name: ModelType.partcategory,
parameters: {},
enabled:
permissionCheck('part_category.view') &&
settingsCheck('SEARCH_PREVIEW_SHOW_CATEGORIES')
},
{
- name: 'stockitem',
- title: t`Stock Items`,
+ name: ModelType.stockitem,
parameters: {
part_detail: true,
location_detail: true
@@ -110,16 +105,14 @@ function buildSearchQueries(): SearchQuery[] {
settingsCheck('SEARCH_PREVIEW_SHOW_STOCK')
},
{
- name: 'stocklocation',
- title: t`Stock Locations`,
+ name: ModelType.stocklocation,
parameters: {},
enabled:
permissionCheck('stock_location.view') &&
settingsCheck('SEARCH_PREVIEW_SHOW_LOCATIONS')
},
{
- name: 'build',
- title: t`Build Orders`,
+ name: ModelType.build,
parameters: {
part_detail: true
},
@@ -128,8 +121,7 @@ function buildSearchQueries(): SearchQuery[] {
settingsCheck('SEARCH_PREVIEW_SHOW_BUILD_ORDERS')
},
{
- name: 'company',
- title: t`Companies`,
+ name: ModelType.company,
parameters: {},
enabled:
(permissionCheck('sales_order.view') ||
@@ -137,8 +129,7 @@ function buildSearchQueries(): SearchQuery[] {
settingsCheck('SEARCH_PREVIEW_SHOW_COMPANIES')
},
{
- name: 'purchaseorder',
- title: t`Purchase Orders`,
+ name: ModelType.purchaseorder,
parameters: {
supplier_detail: true
},
@@ -147,8 +138,7 @@ function buildSearchQueries(): SearchQuery[] {
settingsCheck(`SEARCH_PREVIEW_SHOW_PURCHASE_ORDERS`)
},
{
- name: 'salesorder',
- title: t`Sales Orders`,
+ name: ModelType.salesorder,
parameters: {
customer_detail: true
},
@@ -157,8 +147,7 @@ function buildSearchQueries(): SearchQuery[] {
settingsCheck(`SEARCH_PREVIEW_SHOW_SALES_ORDERS`)
},
{
- name: 'returnorder',
- title: t`Return Orders`,
+ name: ModelType.returnorder,
parameters: {
customer_detail: true
},
@@ -178,19 +167,20 @@ function QueryResultGroup({
onResultClick
}: {
query: SearchQuery;
- onRemove: (query: string) => void;
- onResultClick: (query: string, pk: number) => void;
+ onRemove: (query: ModelType) => void;
+ onResultClick: (query: ModelType, pk: number) => void;
}) {
if (query.results.count == 0) {
return null;
}
+ const model = ModelInformationDict[query.name];
return (
- {query.title}
+ {model.label_multiple}
{' '}
- {query.results.count} results
@@ -320,7 +310,7 @@ export function SearchDrawer({
}, [searchQuery.data]);
// Callback to remove a set of results from the list
- function removeResults(query: string) {
+ function removeResults(query: ModelType) {
setQueryResults(queryResults.filter((q) => q.name != query));
}
@@ -333,9 +323,11 @@ export function SearchDrawer({
const navigate = useNavigate();
// Callback when one of the search results is clicked
- function onResultClick(query: string, pk: number) {
+ function onResultClick(query: ModelType, pk: number) {
closeDrawer();
- navigate(`/${query}/${pk}/`);
+ navigate(
+ ModelInformationDict[query].url_detail.replace(':pk', pk.toString())
+ );
}
return (
diff --git a/src/frontend/src/components/render/Build.tsx b/src/frontend/src/components/render/Build.tsx
index 43cf73767a..175a07b9cb 100644
--- a/src/frontend/src/components/render/Build.tsx
+++ b/src/frontend/src/components/render/Build.tsx
@@ -5,16 +5,12 @@ import { RenderInlineModel } from './Instance';
/**
* Inline rendering of a single BuildOrder instance
*/
-export function RenderBuildOrder({
- buildorder
-}: {
- buildorder: any;
-}): ReactNode {
+export function RenderBuildOrder({ instance }: { instance: any }): ReactNode {
return (
);
}
diff --git a/src/frontend/src/components/render/Company.tsx b/src/frontend/src/components/render/Company.tsx
index 7b4689afa3..06485839f2 100644
--- a/src/frontend/src/components/render/Company.tsx
+++ b/src/frontend/src/components/render/Company.tsx
@@ -5,23 +5,23 @@ import { RenderInlineModel } from './Instance';
/**
* Inline rendering of a single Address instance
*/
-export function RenderAddress({ address }: { address: any }): ReactNode {
+export function RenderAddress({ instance }: { instance: any }): ReactNode {
let text = [
- address.title,
- address.country,
- address.postal_code,
- address.postal_city,
- address.province,
- address.line1,
- address.line2
+ instance.title,
+ instance.country,
+ instance.postal_code,
+ instance.postal_city,
+ instance.province,
+ instance.line1,
+ instance.line2
]
.filter(Boolean)
.join(', ');
return (
);
}
@@ -29,14 +29,14 @@ export function RenderAddress({ address }: { address: any }): ReactNode {
/**
* Inline rendering of a single Company instance
*/
-export function RenderCompany({ company }: { company: any }): ReactNode {
+export function RenderCompany({ instance }: { instance: any }): ReactNode {
// TODO: Handle URL
return (
);
}
@@ -44,25 +44,37 @@ export function RenderCompany({ company }: { company: any }): ReactNode {
/**
* Inline rendering of a single Contact instance
*/
-export function RenderContact({ contact }: { contact: any }): ReactNode {
- return ;
+export function RenderContact({ instance }: { instance: any }): ReactNode {
+ return ;
}
/**
* Inline rendering of a single SupplierPart instance
*/
-export function RenderSupplierPart({
- supplierpart
-}: {
- supplierpart: any;
-}): ReactNode {
+export function RenderSupplierPart({ instance }: { instance: any }): ReactNode {
// TODO: Handle image
// TODO: handle URL
- let supplier = supplierpart.supplier_detail ?? {};
- let part = supplierpart.part_detail ?? {};
+ let supplier = instance.supplier_detail ?? {};
+ let part = instance.part_detail ?? {};
- let text = supplierpart.SKU;
+ let text = instance.SKU;
+
+ if (supplier.name) {
+ text = `${supplier.name} | ${text}`;
+ }
+
+ return ;
+}
+
+/**
+ * Inline rendering of a single ManufacturerPart instance
+ */
+export function ManufacturerPart({ instance }: { instance: any }): ReactNode {
+ let supplier = instance.supplier_detail ?? {};
+ let part = instance.part_detail ?? {};
+
+ let text = instance.SKU;
if (supplier.name) {
text = `${supplier.name} | ${text}`;
diff --git a/src/frontend/src/components/render/Instance.tsx b/src/frontend/src/components/render/Instance.tsx
index b42151784a..0b8b0416f3 100644
--- a/src/frontend/src/components/render/Instance.tsx
+++ b/src/frontend/src/components/render/Instance.tsx
@@ -11,6 +11,7 @@ import {
RenderContact,
RenderSupplierPart
} from './Company';
+import { ModelType } from './ModelType';
import {
RenderPurchaseOrder,
RenderReturnOrder,
@@ -21,6 +22,35 @@ import { RenderPart, RenderPartCategory } from './Part';
import { RenderStockItem, RenderStockLocation } from './Stock';
import { RenderOwner, RenderUser } from './User';
+type EnumDictionary = {
+ [K in T]: U;
+};
+
+/**
+ * Lookup table for rendering a model instance
+ */
+const RendererLookup: EnumDictionary<
+ ModelType,
+ (props: { instance: any }) => ReactNode
+> = {
+ [ModelType.address]: RenderAddress,
+ [ModelType.build]: RenderBuildOrder,
+ [ModelType.company]: RenderCompany,
+ [ModelType.contact]: RenderContact,
+ [ModelType.owner]: RenderOwner,
+ [ModelType.part]: RenderPart,
+ [ModelType.partcategory]: RenderPartCategory,
+ [ModelType.purchaseorder]: RenderPurchaseOrder,
+ [ModelType.returnorder]: RenderReturnOrder,
+ [ModelType.salesorder]: RenderSalesOrder,
+ [ModelType.salesordershipment]: RenderSalesOrderShipment,
+ [ModelType.stocklocation]: RenderStockLocation,
+ [ModelType.stockitem]: RenderStockItem,
+ [ModelType.supplierpart]: RenderSupplierPart,
+ [ModelType.user]: RenderUser,
+ [ModelType.manufacturerpart]: RenderPart
+};
+
// import { ApiFormFieldType } from "../forms/fields/ApiFormField";
/**
@@ -30,48 +60,12 @@ export function RenderInstance({
model,
instance
}: {
- model: string;
+ model: ModelType | undefined;
instance: any;
}): ReactNode {
- switch (model) {
- case 'address':
- return ;
- case 'build':
- return ;
- case 'company':
- return ;
- case 'contact':
- return ;
- case 'owner':
- return ;
- case 'part':
- return ;
- case 'partcategory':
- return ;
- case 'purchaseorder':
- return ;
- case 'returnorder':
- return ;
- case 'salesoder':
- return ;
- case 'salesordershipment':
- return ;
- case 'stocklocation':
- return ;
- case 'stockitem':
- return ;
- case 'supplierpart':
- return ;
- case 'user':
- return ;
- default:
- // Unknown model
- return (
-
- <>>
-
- );
- }
+ if (model === undefined) return ;
+ const RenderComponent = RendererLookup[model];
+ return ;
}
/**
@@ -101,3 +95,15 @@ export function RenderInlineModel({
);
}
+
+export function UnknownRenderer({
+ model
+}: {
+ model: ModelType | undefined;
+}): ReactNode {
+ return (
+
+ <>>
+
+ );
+}
diff --git a/src/frontend/src/components/render/ModelType.tsx b/src/frontend/src/components/render/ModelType.tsx
new file mode 100644
index 0000000000..17a671fd40
--- /dev/null
+++ b/src/frontend/src/components/render/ModelType.tsx
@@ -0,0 +1,130 @@
+import { t } from '@lingui/macro';
+
+export enum ModelType {
+ part = 'part',
+ supplierpart = 'supplierpart',
+ manufacturerpart = 'manufacturerpart',
+ partcategory = 'partcategory',
+ stockitem = 'stockitem',
+ stocklocation = 'stocklocation',
+ build = 'build',
+ company = 'company',
+ purchaseorder = 'purchaseorder',
+ salesorder = 'salesorder',
+ salesordershipment = 'salesordershipment',
+ returnorder = 'returnorder',
+ address = 'address',
+ contact = 'contact',
+ owner = 'owner',
+ user = 'user'
+}
+
+interface ModelInformatonInterface {
+ label: string;
+ label_multiple: string;
+ url_overview: string;
+ url_detail: string;
+}
+
+type ModelDictory = {
+ [key in keyof typeof ModelType]: ModelInformatonInterface;
+};
+
+export const ModelInformationDict: ModelDictory = {
+ part: {
+ label: t`Part`,
+ label_multiple: t`Parts`,
+ url_overview: '/part',
+ url_detail: '/part/:pk/'
+ },
+ supplierpart: {
+ label: t`Supplier Part`,
+ label_multiple: t`Supplier Parts`,
+ url_overview: '/supplierpart',
+ url_detail: '/supplierpart/:pk/'
+ },
+ manufacturerpart: {
+ label: t`Manufacturer Part`,
+ label_multiple: t`Manufacturer Parts`,
+ url_overview: '/manufacturerpart',
+ url_detail: '/manufacturerpart/:pk/'
+ },
+ partcategory: {
+ label: t`Part Category`,
+ label_multiple: t`Part Categories`,
+ url_overview: '/partcategory',
+ url_detail: '/partcategory/:pk/'
+ },
+ stockitem: {
+ label: t`Stock Item`,
+ label_multiple: t`Stock Items`,
+ url_overview: '/stockitem',
+ url_detail: '/stockitem/:pk/'
+ },
+ stocklocation: {
+ label: t`Stock Location`,
+ label_multiple: t`Stock Locations`,
+ url_overview: '/stocklocation',
+ url_detail: '/stocklocation/:pk/'
+ },
+ build: {
+ label: t`Build`,
+ label_multiple: t`Builds`,
+ url_overview: '/build',
+ url_detail: '/build/:pk/'
+ },
+ company: {
+ label: t`Company`,
+ label_multiple: t`Companies`,
+ url_overview: '/company',
+ url_detail: '/company/:pk/'
+ },
+ purchaseorder: {
+ label: t`Purchase Order`,
+ label_multiple: t`Purchase Orders`,
+ url_overview: '/purchaseorder',
+ url_detail: '/purchaseorder/:pk/'
+ },
+ salesorder: {
+ label: t`Sales Order`,
+ label_multiple: t`Sales Orders`,
+ url_overview: '/salesorder',
+ url_detail: '/salesorder/:pk/'
+ },
+ salesordershipment: {
+ label: t`Sales Order Shipment`,
+ label_multiple: t`Sales Order Shipments`,
+ url_overview: '/salesordershipment',
+ url_detail: '/salesordershipment/:pk/'
+ },
+ returnorder: {
+ label: t`Return Order`,
+ label_multiple: t`Return Orders`,
+ url_overview: '/returnorder',
+ url_detail: '/returnorder/:pk/'
+ },
+ address: {
+ label: t`Address`,
+ label_multiple: t`Addresses`,
+ url_overview: '/address',
+ url_detail: '/address/:pk/'
+ },
+ contact: {
+ label: t`Contact`,
+ label_multiple: t`Contacts`,
+ url_overview: '/contact',
+ url_detail: '/contact/:pk/'
+ },
+ owner: {
+ label: t`Owner`,
+ label_multiple: t`Owners`,
+ url_overview: '/owner',
+ url_detail: '/owner/:pk/'
+ },
+ user: {
+ label: t`User`,
+ label_multiple: t`Users`,
+ url_overview: '/user',
+ url_detail: '/user/:pk/'
+ }
+};
diff --git a/src/frontend/src/components/render/Order.tsx b/src/frontend/src/components/render/Order.tsx
index 92157cf70b..e9d990be5e 100644
--- a/src/frontend/src/components/render/Order.tsx
+++ b/src/frontend/src/components/render/Order.tsx
@@ -6,14 +6,18 @@ import { RenderInlineModel } from './Instance';
/**
* Inline rendering of a single PurchaseOrder instance
*/
-export function RenderPurchaseOrder({ order }: { order: any }): ReactNode {
- let supplier = order.supplier_detail || {};
+export function RenderPurchaseOrder({
+ instance
+}: {
+ instance: any;
+}): ReactNode {
+ let supplier = instance.supplier_detail || {};
// TODO: Handle URL
return (
);
@@ -22,13 +26,13 @@ export function RenderPurchaseOrder({ order }: { order: any }): ReactNode {
/**
* Inline rendering of a single ReturnOrder instance
*/
-export function RenderReturnOrder({ order }: { order: any }): ReactNode {
- let customer = order.customer_detail || {};
+export function RenderReturnOrder({ instance }: { instance: any }): ReactNode {
+ let customer = instance.customer_detail || {};
return (
);
@@ -37,15 +41,15 @@ export function RenderReturnOrder({ order }: { order: any }): ReactNode {
/**
* Inline rendering of a single SalesOrder instance
*/
-export function RenderSalesOrder({ order }: { order: any }): ReactNode {
- let customer = order.customer_detail || {};
+export function RenderSalesOrder({ instance }: { instance: any }): ReactNode {
+ let customer = instance.customer_detail || {};
// TODO: Handle URL
return (
);
@@ -55,16 +59,16 @@ export function RenderSalesOrder({ order }: { order: any }): ReactNode {
* Inline rendering of a single SalesOrderAllocation instance
*/
export function RenderSalesOrderShipment({
- shipment
+ instance
}: {
- shipment: any;
+ instance: any;
}): ReactNode {
- let order = shipment.sales_order_detail || {};
+ let order = instance.sales_order_detail || {};
return (
);
}
diff --git a/src/frontend/src/components/render/Part.tsx b/src/frontend/src/components/render/Part.tsx
index bd570085cc..cf0ec5d282 100644
--- a/src/frontend/src/components/render/Part.tsx
+++ b/src/frontend/src/components/render/Part.tsx
@@ -5,12 +5,12 @@ import { RenderInlineModel } from './Instance';
/**
* Inline rendering of a single Part instance
*/
-export function RenderPart({ part }: { part: any }): ReactNode {
+export function RenderPart({ instance }: { instance: any }): ReactNode {
return (
);
}
@@ -18,15 +18,15 @@ export function RenderPart({ part }: { part: any }): ReactNode {
/**
* Inline rendering of a PartCategory instance
*/
-export function RenderPartCategory({ category }: { category: any }): ReactNode {
+export function RenderPartCategory({ instance }: { instance: any }): ReactNode {
// TODO: Handle URL
- let lvl = '-'.repeat(category.level || 0);
+ let lvl = '-'.repeat(instance.level || 0);
return (
);
}
diff --git a/src/frontend/src/components/render/Stock.tsx b/src/frontend/src/components/render/Stock.tsx
index 4894b2f255..9537480749 100644
--- a/src/frontend/src/components/render/Stock.tsx
+++ b/src/frontend/src/components/render/Stock.tsx
@@ -6,24 +6,24 @@ import { RenderInlineModel } from './Instance';
* Inline rendering of a single StockLocation instance
*/
export function RenderStockLocation({
- location
+ instance
}: {
- location: any;
+ instance: any;
}): ReactNode {
return (
);
}
-export function RenderStockItem({ item }: { item: any }): ReactNode {
+export function RenderStockItem({ instance }: { instance: any }): ReactNode {
return (
);
}
diff --git a/src/frontend/src/components/render/User.tsx b/src/frontend/src/components/render/User.tsx
index ca099c04e6..6bad2a3011 100644
--- a/src/frontend/src/components/render/User.tsx
+++ b/src/frontend/src/components/render/User.tsx
@@ -2,17 +2,17 @@ import { ReactNode } from 'react';
import { RenderInlineModel } from './Instance';
-export function RenderOwner({ owner }: { owner: any }): ReactNode {
+export function RenderOwner({ instance }: { instance: any }): ReactNode {
// TODO: Icon based on user / group status?
- return ;
+ return ;
}
-export function RenderUser({ user }: { user: any }): ReactNode {
+export function RenderUser({ instance }: { instance: any }): ReactNode {
return (
);
}