diff --git a/src/frontend/src/components/previews/PreviewType.tsx b/src/frontend/src/components/previews/PreviewType.tsx
index 8131348bf0..3dfb2eb19a 100644
--- a/src/frontend/src/components/previews/PreviewType.tsx
+++ b/src/frontend/src/components/previews/PreviewType.tsx
@@ -1,5 +1,7 @@
import { ModelType } from '@lib/enums/ModelType';
import type { ReactNode } from 'react';
+import { BuildOrderPreviewComponent } from './models/BuildOrderPreview';
+import { CompanyPreviewComponent } from './models/CompanyPreview';
import { ManufacturerPartPreviewComponent } from './models/ManufacturerPartPreview';
import { PartPreviewComponent } from './models/PartPreview';
import { PurchaseOrderPreviewComponent } from './models/PurchaseOrderPreview';
@@ -43,6 +45,10 @@ export function getPreviewComponentForModel({
return SupplierPartPreviewComponent({ instance, modelId });
case ModelType.manufacturerpart:
return ManufacturerPartPreviewComponent({ instance, modelId });
+ case ModelType.company:
+ return CompanyPreviewComponent({ instance, modelId });
+ case ModelType.build:
+ return BuildOrderPreviewComponent({ instance, modelId });
default:
return null;
}
diff --git a/src/frontend/src/components/previews/models/BuildOrderPreview.tsx b/src/frontend/src/components/previews/models/BuildOrderPreview.tsx
new file mode 100644
index 0000000000..5735da0c80
--- /dev/null
+++ b/src/frontend/src/components/previews/models/BuildOrderPreview.tsx
@@ -0,0 +1,25 @@
+import { t } from '@lingui/core/macro';
+import { BuildOrderDetailsPanel } from '../../../pages/build/BuildOrderDetailsPanel';
+import type { PreviewType } from '../PreviewType';
+
+export function BuildOrderPreviewComponent({
+ instance,
+ modelId
+}: Readonly<{
+ instance: any;
+ modelId: number;
+}>): PreviewType {
+ const part = instance?.part_detail?.full_name ?? instance?.part_detail?.name;
+ const ref = instance?.reference ?? `#${modelId}`;
+
+ let title = `${t`Build Order`} ${ref}`;
+
+ if (part) {
+ title += ` (${part})`;
+ }
+
+ return {
+ title,
+ preview:
+ };
+}
diff --git a/src/frontend/src/components/previews/models/CompanyPreview.tsx b/src/frontend/src/components/previews/models/CompanyPreview.tsx
new file mode 100644
index 0000000000..772ad33944
--- /dev/null
+++ b/src/frontend/src/components/previews/models/CompanyPreview.tsx
@@ -0,0 +1,18 @@
+import { t } from '@lingui/core/macro';
+import { CompanyDetailsPanel } from '../../../pages/company/CompanyDetailsPanel';
+import type { PreviewType } from '../PreviewType';
+
+export function CompanyPreviewComponent({
+ instance,
+ modelId
+}: Readonly<{
+ instance: any;
+ modelId: number;
+}>): PreviewType {
+ const name = instance?.name ?? `#${modelId}`;
+
+ return {
+ title: `${t`Company`} - ${name}`,
+ preview:
+ };
+}
diff --git a/src/frontend/src/pages/build/BuildDetail.tsx b/src/frontend/src/pages/build/BuildDetail.tsx
index 85689fe3cb..bd11686939 100644
--- a/src/frontend/src/pages/build/BuildDetail.tsx
+++ b/src/frontend/src/pages/build/BuildDetail.tsx
@@ -1,5 +1,5 @@
import { t } from '@lingui/core/macro';
-import { Alert, Grid, Skeleton, Stack, Text } from '@mantine/core';
+import { Alert, Skeleton, Stack, Text } from '@mantine/core';
import {
IconChecklist,
IconCircleCheck,
@@ -21,19 +21,12 @@ import { ModelType } from '@lib/enums/ModelType';
import { UserRoles } from '@lib/enums/Roles';
import { apiUrl } from '@lib/functions/Api';
import { getDetailUrl } from '@lib/functions/Navigation';
-import { TagsList } from '@lib/index';
import type { ApiFormFieldSet } from '@lib/types/Forms';
import type { PanelType } from '@lib/types/Panel';
import AdminButton from '../../components/buttons/AdminButton';
import PrimaryActionButton from '../../components/buttons/PrimaryActionButton';
import { PrintingActions } from '../../components/buttons/PrintingActions';
-import {
- type DetailsField,
- DetailsTable
-} from '../../components/details/Details';
import DetailsBadge from '../../components/details/DetailsBadge';
-import { DetailsImage } from '../../components/details/DetailsImage';
-import { ItemDetailsGrid } from '../../components/details/ItemDetails';
import {
BarcodeActionDropdown,
CancelItemAction,
@@ -66,6 +59,7 @@ import BuildOutputTable from '../../tables/build/BuildOutputTable';
import PartTestResultTable from '../../tables/part/PartTestResultTable';
import { PurchaseOrderTable } from '../../tables/purchasing/PurchaseOrderTable';
import { StockItemTable } from '../../tables/stock/StockItemTable';
+import { BuildOrderDetailsPanel } from './BuildOrderDetailsPanel';
function NoItems() {
return (
@@ -235,242 +229,19 @@ export default function BuildDetail() {
refetchOnMount: true
});
- const { instance: partRequirements, instanceQuery: partRequirementsQuery } =
- useInstance({
- endpoint: ApiEndpoints.part_requirements,
- pk: build?.part,
- hasPrimaryKey: true,
- defaultValue: {}
- });
-
- const detailsPanel = useMemo(() => {
- if (instanceQuery.isFetching) {
- return ;
- }
-
- const data = {
- ...build,
- can_build: partRequirements?.can_build ?? 0
- };
-
- const tl: DetailsField[] = [
- {
- type: 'link',
- name: 'part',
- label: t`Part`,
- model: ModelType.part
- },
- {
- type: 'text',
- name: 'part_detail.IPN',
- icon: 'part',
- label: t`IPN`,
- hidden: !build.part_detail?.IPN,
- copy: true
- },
- {
- type: 'string',
- name: 'part_detail.revision',
- icon: 'revision',
- label: t`Revision`,
- hidden: !build.part_detail?.revision,
- copy: true
- },
- {
- type: 'status',
- name: 'status',
- label: t`Status`,
- model: ModelType.build
- },
- {
- type: 'status',
- name: 'status_custom_key',
- label: t`Custom Status`,
- model: ModelType.build,
- icon: 'status',
- hidden:
- !build.status_custom_key || build.status_custom_key == build.status
- },
- {
- type: 'boolean',
- name: 'external',
- label: t`External`,
- icon: 'manufacturers',
- hidden: !build.external
- },
- {
- type: 'text',
- name: 'reference',
- label: t`Reference`,
- copy: true
- },
- {
- type: 'text',
- name: 'title',
- label: t`Description`,
- icon: 'description',
- copy: true
- },
- {
- type: 'link',
- name: 'parent',
- icon: 'builds',
- label: t`Parent Build`,
- model_field: 'reference',
- model: ModelType.build,
- hidden: !build.parent
- }
- ];
-
- const tr: DetailsField[] = [
- {
- type: 'number',
- name: 'quantity',
- label: t`Build Quantity`
- },
- {
- type: 'number',
- name: 'can_build',
- unit: build.part_detail?.units,
- label: t`Can Build`,
- hidden: partRequirements?.can_build === undefined
- },
- {
- type: 'progressbar',
- name: 'completed',
- icon: 'progress',
- total: build.quantity,
- progress: build.completed,
- label: t`Completed Outputs`
- },
- {
- type: 'link',
- name: 'sales_order',
- label: t`Sales Order`,
- icon: 'sales_orders',
- model: ModelType.salesorder,
- model_field: 'reference',
- hidden: !build.sales_order
- }
- ];
-
- const bl: DetailsField[] = [
- {
- type: 'text',
- name: 'issued_by',
- label: t`Issued By`,
- icon: 'user',
- badge: 'user',
- hidden: !build.issued_by
- },
- {
- type: 'text',
- name: 'responsible',
- label: t`Responsible`,
- badge: 'owner',
- hidden: !build.responsible
- },
- {
- type: 'text',
- name: 'project_code_label',
- label: t`Project Code`,
- icon: 'reference',
- copy: true,
- hidden: !build.project_code
- },
- {
- type: 'link',
- name: 'take_from',
- icon: 'location',
- model: ModelType.stocklocation,
- label: t`Source Location`,
- backup_value: t`Any location`
- },
- {
- type: 'link',
- name: 'destination',
- icon: 'location',
- model: ModelType.stocklocation,
- label: t`Destination Location`,
- hidden: !build.destination
- },
- {
- type: 'text',
- name: 'batch',
- label: t`Batch Code`,
- hidden: !build.batch,
- copy: true
- }
- ];
-
- const br: DetailsField[] = [
- {
- type: 'date',
- name: 'creation_date',
- label: t`Created`,
- icon: 'calendar',
- copy: true,
- hidden: !build.creation_date
- },
- {
- type: 'date',
- name: 'start_date',
- label: t`Start Date`,
- icon: 'calendar',
- copy: true,
- hidden: !build.start_date
- },
- {
- type: 'date',
- name: 'target_date',
- label: t`Target Date`,
- icon: 'calendar',
- copy: true,
- hidden: !build.target_date
- },
- {
- type: 'date',
- name: 'completion_date',
- label: t`Completed`,
- icon: 'calendar',
- copy: true,
- hidden: !build.completion_date
- }
- ];
-
- return (
-
-
-
-
-
-
-
-
-
-
-
- );
- }, [build, instanceQuery, partRequirements, partRequirementsQuery]);
-
const buildPanels: PanelType[] = useMemo(() => {
return [
{
name: 'details',
label: t`Build Details`,
icon: ,
- content: detailsPanel
+ content: (
+
+ )
},
{
name: 'line-items',
@@ -600,7 +371,7 @@ export default function BuildDetail() {
build,
id,
user,
- partRequirements,
+
buildStatus,
globalSettings,
showChildBuilds,
diff --git a/src/frontend/src/pages/build/BuildOrderDetailsPanel.tsx b/src/frontend/src/pages/build/BuildOrderDetailsPanel.tsx
new file mode 100644
index 0000000000..4e09e1b59a
--- /dev/null
+++ b/src/frontend/src/pages/build/BuildOrderDetailsPanel.tsx
@@ -0,0 +1,262 @@
+import { t } from '@lingui/core/macro';
+import { Grid, Skeleton, Stack } from '@mantine/core';
+import { useMemo } from 'react';
+
+import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
+import { ModelType } from '@lib/enums/ModelType';
+import { UserRoles } from '@lib/enums/Roles';
+import { apiUrl } from '@lib/functions/Api';
+import { TagsList } from '@lib/index';
+
+import {
+ type DetailsField,
+ DetailsTable
+} from '../../components/details/Details';
+import { DetailsImage } from '../../components/details/DetailsImage';
+import { ItemDetailsGrid } from '../../components/details/ItemDetails';
+import { useParameterDetailsGrid } from '../../components/details/ParameterDetailsGrid';
+import { useInstance } from '../../hooks/UseInstance';
+
+export function BuildOrderDetailsPanel({
+ instance,
+ allowImageEdit = false,
+ refreshInstance
+}: Readonly<{
+ instance: any;
+ allowImageEdit?: boolean;
+ refreshInstance?: () => void;
+}>) {
+ const { instance: partRequirements } = useInstance({
+ endpoint: ApiEndpoints.part_requirements,
+ pk: instance?.part,
+ hasPrimaryKey: true,
+ defaultValue: {}
+ });
+
+ const data = useMemo(
+ () => ({ ...instance, can_build: partRequirements?.can_build ?? 0 }),
+ [instance, partRequirements]
+ );
+
+ const tl: DetailsField[] = [
+ {
+ type: 'link',
+ name: 'part',
+ label: t`Part`,
+ model: ModelType.part
+ },
+ {
+ type: 'text',
+ name: 'part_detail.IPN',
+ icon: 'part',
+ label: t`IPN`,
+ hidden: !instance?.part_detail?.IPN,
+ copy: true
+ },
+ {
+ type: 'string',
+ name: 'part_detail.revision',
+ icon: 'revision',
+ label: t`Revision`,
+ hidden: !instance?.part_detail?.revision,
+ copy: true
+ },
+ {
+ type: 'status',
+ name: 'status',
+ label: t`Status`,
+ model: ModelType.build
+ },
+ {
+ type: 'status',
+ name: 'status_custom_key',
+ label: t`Custom Status`,
+ model: ModelType.build,
+ icon: 'status',
+ hidden:
+ !instance?.status_custom_key ||
+ instance?.status_custom_key == instance?.status
+ },
+ {
+ type: 'boolean',
+ name: 'external',
+ label: t`External`,
+ icon: 'manufacturers',
+ hidden: !instance?.external
+ },
+ {
+ type: 'text',
+ name: 'reference',
+ label: t`Reference`,
+ copy: true
+ },
+ {
+ type: 'text',
+ name: 'title',
+ label: t`Description`,
+ icon: 'description',
+ copy: true
+ },
+ {
+ type: 'link',
+ name: 'parent',
+ icon: 'builds',
+ label: t`Parent Build`,
+ model_field: 'reference',
+ model: ModelType.build,
+ hidden: !instance?.parent
+ }
+ ];
+
+ const tr: DetailsField[] = [
+ {
+ type: 'number',
+ name: 'quantity',
+ label: t`Build Quantity`
+ },
+ {
+ type: 'number',
+ name: 'can_build',
+ unit: instance?.part_detail?.units,
+ label: t`Can Build`,
+ hidden: partRequirements?.can_build === undefined
+ },
+ {
+ type: 'progressbar',
+ name: 'completed',
+ icon: 'progress',
+ total: instance?.quantity,
+ progress: instance?.completed,
+ label: t`Completed Outputs`
+ },
+ {
+ type: 'link',
+ name: 'sales_order',
+ label: t`Sales Order`,
+ icon: 'sales_orders',
+ model: ModelType.salesorder,
+ model_field: 'reference',
+ hidden: !instance?.sales_order
+ }
+ ];
+
+ const bl: DetailsField[] = [
+ {
+ type: 'text',
+ name: 'issued_by',
+ label: t`Issued By`,
+ icon: 'user',
+ badge: 'user',
+ hidden: !instance?.issued_by
+ },
+ {
+ type: 'text',
+ name: 'responsible',
+ label: t`Responsible`,
+ badge: 'owner',
+ hidden: !instance?.responsible
+ },
+ {
+ type: 'text',
+ name: 'project_code_label',
+ label: t`Project Code`,
+ icon: 'reference',
+ copy: true,
+ hidden: !instance?.project_code
+ },
+ {
+ type: 'link',
+ name: 'take_from',
+ icon: 'location',
+ model: ModelType.stocklocation,
+ label: t`Source Location`,
+ backup_value: t`Any location`
+ },
+ {
+ type: 'link',
+ name: 'destination',
+ icon: 'location',
+ model: ModelType.stocklocation,
+ label: t`Destination Location`,
+ hidden: !instance?.destination
+ },
+ {
+ type: 'text',
+ name: 'batch',
+ label: t`Batch Code`,
+ hidden: !instance?.batch,
+ copy: true
+ }
+ ];
+
+ const br: DetailsField[] = [
+ {
+ type: 'date',
+ name: 'creation_date',
+ label: t`Created`,
+ icon: 'calendar',
+ copy: true,
+ hidden: !instance?.creation_date
+ },
+ {
+ type: 'date',
+ name: 'start_date',
+ label: t`Start Date`,
+ icon: 'calendar',
+ copy: true,
+ hidden: !instance?.start_date
+ },
+ {
+ type: 'date',
+ name: 'target_date',
+ label: t`Target Date`,
+ icon: 'calendar',
+ copy: true,
+ hidden: !instance?.target_date
+ },
+ {
+ type: 'date',
+ name: 'completion_date',
+ label: t`Completed`,
+ icon: 'calendar',
+ copy: true,
+ hidden: !instance?.completion_date
+ }
+ ];
+
+ const parametersTable = useParameterDetailsGrid({
+ model_type: ModelType.build,
+ model_id: instance?.pk
+ });
+
+ if (!instance?.pk) return ;
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/frontend/src/pages/company/CompanyDetail.tsx b/src/frontend/src/pages/company/CompanyDetail.tsx
index 4a55122c77..c1501b6b6d 100644
--- a/src/frontend/src/pages/company/CompanyDetail.tsx
+++ b/src/frontend/src/pages/company/CompanyDetail.tsx
@@ -1,5 +1,5 @@
import { t } from '@lingui/core/macro';
-import { Grid, Skeleton, Stack } from '@mantine/core';
+import { Skeleton, Stack } from '@mantine/core';
import {
IconBuildingWarehouse,
IconInfoCircle,
@@ -17,18 +17,10 @@ import { useNavigate, useParams } from 'react-router-dom';
import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
import { ModelType } from '@lib/enums/ModelType';
import { UserRoles } from '@lib/enums/Roles';
-import { apiUrl } from '@lib/functions/Api';
-import { TagsList } from '@lib/index';
import type { PanelType } from '@lib/types/Panel';
import AdminButton from '../../components/buttons/AdminButton';
import { PrintingActions } from '../../components/buttons/PrintingActions';
-import {
- type DetailsField,
- DetailsTable
-} from '../../components/details/Details';
import DetailsBadge from '../../components/details/DetailsBadge';
-import { DetailsImage } from '../../components/details/DetailsImage';
-import { ItemDetailsGrid } from '../../components/details/ItemDetails';
import {
DeleteItemAction,
EditItemAction,
@@ -56,6 +48,7 @@ import { SupplierPartTable } from '../../tables/purchasing/SupplierPartTable';
import { ReturnOrderTable } from '../../tables/sales/ReturnOrderTable';
import { SalesOrderTable } from '../../tables/sales/SalesOrderTable';
import { StockItemTable } from '../../tables/stock/StockItemTable';
+import { CompanyDetailsPanel } from './CompanyDetailsPanel';
export type CompanyDetailProps = {
title: string;
@@ -85,100 +78,15 @@ export default function CompanyDetail(props: Readonly) {
refetchOnMount: true
});
- const detailsPanel = useMemo(() => {
- if (instanceQuery.isFetching) {
- return ;
- }
-
- const tl: DetailsField[] = [
- {
- type: 'text',
- name: 'description',
- label: t`Description`,
- copy: true
- },
- {
- type: 'link',
- name: 'website',
- label: t`Website`,
- external: true,
- copy: true,
- hidden: !company.website
- },
- {
- type: 'text',
- name: 'phone',
- label: t`Phone Number`,
- copy: true,
- hidden: !company.phone
- },
- {
- type: 'text',
- name: 'email',
- label: t`Email Address`,
- copy: true,
- hidden: !company.email
- },
- {
- type: 'text',
- name: 'tax_id',
- label: t`Tax ID`,
- copy: true,
- hidden: !company.tax_id
- }
- ];
-
- const tr: DetailsField[] = [
- {
- type: 'string',
- name: 'currency',
- label: t`Default Currency`
- },
- {
- type: 'boolean',
- name: 'is_supplier',
- label: t`Supplier`,
- icon: 'suppliers'
- },
- {
- type: 'boolean',
- name: 'is_manufacturer',
- label: t`Manufacturer`,
- icon: 'manufacturers'
- },
- {
- type: 'boolean',
- name: 'is_customer',
- label: t`Customer`,
- icon: 'customers'
- }
- ];
-
- return (
-
-
-
-
-
-
-
-
-
-
-
- );
- }, [company, instanceQuery]);
+ const detailsPanel = instanceQuery.isFetching ? (
+
+ ) : (
+
+ );
const companyPanels: PanelType[] = useMemo(() => {
return [
diff --git a/src/frontend/src/pages/company/CompanyDetailsPanel.tsx b/src/frontend/src/pages/company/CompanyDetailsPanel.tsx
new file mode 100644
index 0000000000..b98d67515c
--- /dev/null
+++ b/src/frontend/src/pages/company/CompanyDetailsPanel.tsx
@@ -0,0 +1,119 @@
+import { t } from '@lingui/core/macro';
+import { Grid, Skeleton, Stack } from '@mantine/core';
+
+import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
+import { UserRoles } from '@lib/enums/Roles';
+import { apiUrl } from '@lib/functions/Api';
+import { TagsList } from '@lib/index';
+
+import {
+ type DetailsField,
+ DetailsTable
+} from '../../components/details/Details';
+import { DetailsImage } from '../../components/details/DetailsImage';
+import { ItemDetailsGrid } from '../../components/details/ItemDetails';
+
+export function CompanyDetailsPanel({
+ instance,
+ allowImageEdit = false,
+ refreshInstance
+}: Readonly<{
+ instance: any;
+ allowImageEdit?: boolean;
+ refreshInstance?: () => void;
+}>) {
+ const tl: DetailsField[] = [
+ {
+ type: 'text',
+ name: 'description',
+ label: t`Description`,
+ copy: true
+ },
+ {
+ type: 'link',
+ name: 'website',
+ label: t`Website`,
+ external: true,
+ copy: true,
+ hidden: !instance?.website
+ },
+ {
+ type: 'text',
+ name: 'phone',
+ label: t`Phone Number`,
+ copy: true,
+ hidden: !instance?.phone
+ },
+ {
+ type: 'text',
+ name: 'email',
+ label: t`Email Address`,
+ copy: true,
+ hidden: !instance?.email
+ },
+ {
+ type: 'text',
+ name: 'tax_id',
+ label: t`Tax ID`,
+ copy: true,
+ hidden: !instance?.tax_id
+ }
+ ];
+
+ const tr: DetailsField[] = [
+ {
+ type: 'string',
+ name: 'currency',
+ label: t`Default Currency`
+ },
+ {
+ type: 'boolean',
+ name: 'is_supplier',
+ label: t`Supplier`,
+ icon: 'suppliers'
+ },
+ {
+ type: 'boolean',
+ name: 'is_manufacturer',
+ label: t`Manufacturer`,
+ icon: 'manufacturers'
+ },
+ {
+ type: 'boolean',
+ name: 'is_customer',
+ label: t`Customer`,
+ icon: 'customers'
+ }
+ ];
+
+ if (!instance?.pk) return ;
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/frontend/src/tables/company/CompanyTable.tsx b/src/frontend/src/tables/company/CompanyTable.tsx
index a3ebc82422..ccd0cc61c0 100644
--- a/src/frontend/src/tables/company/CompanyTable.tsx
+++ b/src/frontend/src/tables/company/CompanyTable.tsx
@@ -8,7 +8,7 @@ import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
import { ModelType } from '@lib/enums/ModelType';
import { UserRoles } from '@lib/enums/Roles';
import { apiUrl } from '@lib/functions/Api';
-import { navigateToLink } from '@lib/functions/Navigation';
+import { eventModified, navigateToLink } from '@lib/functions/Navigation';
import useTable from '@lib/hooks/UseTable';
import type { TableFilter } from '@lib/types/Filters';
import { companyFields } from '../../forms/CompanyForms';
@@ -16,6 +16,7 @@ import {
useCreateApiFormModal,
useEditApiFormModal
} from '../../hooks/UseForm';
+import { usePreviewDrawerState } from '../../states/PreviewDrawerState';
import { useUserState } from '../../states/UserState';
import {
BooleanColumn,
@@ -49,6 +50,7 @@ export function CompanyTable({
const navigate = useNavigate();
const user = useUserState();
+ const previewDrawer = usePreviewDrawerState();
const columns = useMemo(() => {
return [
@@ -166,10 +168,16 @@ export function CompanyTable({
params: {
...params
},
- onRowClick: (record: any, index: number, event: any) => {
- if (record.pk) {
- const base = path ?? 'company';
- navigateToLink(`/${base}/${record.pk}`, navigate, event);
+ onRowClick: (record: any, _index: number, event: any) => {
+ if (!record.pk) return;
+ if (eventModified(event as any)) {
+ navigateToLink(
+ `/${path ?? 'company'}/${record.pk}`,
+ navigate,
+ event
+ );
+ } else {
+ previewDrawer.openPreview(ModelType.company, Number(record.pk));
}
},
modelType: ModelType.company,